1. 项目概述:为什么车载 Android 的 USB 不是“插上就能用”?
在车载系统开发一线干了十多年,我经手过从 2013 年 Android 4.2 车机到 2024 年 Android 14 智能座舱的全部代际迭代。很多人以为 Android 车载 USB 就是“插个 U 盘放音乐”或者“接个串口调试仪读数据”,但真实场景远比这复杂得多——USB Host 模式下,你面对的不是通用 PC,而是一个被深度定制、权限收紧、HAL 层重构、甚至内核模块被裁剪过的嵌入式 Linux 环境。标题里提到的
USB Host、USB 串口、USB-CAN、HID
,表面看是四种外设类型,实则对应四类完全不同的底层交互路径:Host 模式是系统级能力开关;串口依赖 CDC ACM 或 FTDI 驱动栈兼容性;USB-CAN 本质是 vendor-specific class 设备,需自定义 HAL 接口;HID 则横跨 kernel input 子系统与 userspace event 解析两层。而
Android 系统 API
这个词,恰恰是最容易被误解的部分——它不是一套开箱即用的 SDK,而是由
UsbManager
、
UsbDeviceConnection
、
UsbRequest
、
InputManager
、
InputEvent
等分散在 framework 层的组件拼成的“能力拼图”,每一块都受 SELinux 策略、签名权限、vendor overlay、甚至 OEM 自定义 service 的层层制约。我见过太多团队卡在“设备枚举成功但 open 失败”“hid descriptor 解析正确却收不到按键事件”“CAN 帧能发不能收”这类问题上,最后发现根源不在代码,而在
/system/etc/usb_config.xml
里一行被注释掉的
<device>
配置,或
init.rc
中缺失的
chmod 0666 /dev/ttyUSB0
。所以这篇笔记不讲理论,只记录我在量产项目中踩过的坑、验证过的路径、可复用的配置模板和必须绕开的雷区。如果你正在做车规级 USB 外设接入(比如胎压监测 CAN 模块、方向盘 HID 按键、OBD-II 串口诊断仪),这篇内容就是你调试前该先读三遍的“避障地图”。
2. USB Host 模式:从硬件识别到用户授权的全链路拆解
2.1 硬件层:车载 USB Port 的物理约束与供电逻辑
车载 USB Port 和消费电子完全不同。消费端 USB-A 口默认提供 500mA(USB 2.0)或 900mA(USB 3.0),而车规级 USB-A 口通常被限制在 100–200mA,且多数仅支持 USB 2.0 速率。更关键的是,
车载 USB Port 往往不具备真正的 Host Controller 功能
——很多中低端车机采用 USB OTG(On-The-Go)芯片,通过 ID 引脚电平判断角色,但车规级设计常将 ID 引脚固定为 GND,强制进入 Device 模式。这意味着即使你插的是 USB Host 设备(如 USB-CAN 适配器),系统也可能根本无法识别。实测中,我们曾用示波器抓取某款高通 8155 车机主板 USB2.0 PHY 的 D+/D- 波形,发现插入 USB 串口转换器后无任何握手信号,最终确认是主板 USB Port 的 VBUS 供电被硬件开关切断,需通过 GPIO 控制使能。解决方案不是改代码,而是让硬件同事在
board-xxx.dtsi
中添加:
&usb_otg {
vbus-supply = <&pm8994_l12>;
status = "okay";
};
并确保
pm8994_l12
对应的 LDO 在 boot 阶段已使能。这个细节在 AOSP 文档里绝不会提,但却是车载项目启动 USB Host 的第一道门槛。
2.2 内核层:USB Core 与 Vendor Driver 的加载时机与依赖
Android 车载系统内核通常基于 Linux LTS 版本(如 5.10/5.15),但 OEM 会裁剪大量 USB 相关模块。重点检查以下三点:
-
USB Core 必须启用
:
CONFIG_USB=y,CONFIG_USB_DEVICEFS=y,CONFIG_USB_DEVICE_CLASS=y。若CONFIG_USB_DEVICEFS未启用,/dev/bus/usb/目录将不存在,UsbManager.getDeviceList()永远返回空 Map。 -
Host Controller Driver
:x86 平台多用
xhci_hcd,ARM 平台常见dwc3或ehci-hcd。执行lsmod | grep -i usb查看是否加载。若无输出,需确认dwc3-of-simple是否在 dts 中正确引用。 -
Vendor-specific Driver
:这是最易被忽略的环节。例如 PL2303 串口芯片需
pl2303.ko,CH340 需ch341.ko,而 USB-CAN 常见的 MCP2517FD 需mcp251xfd.ko。这些模块往往未编译进内核镜像,需以.ko文件形式随 OTA 包下发。我们曾因ch341.ko缺失导致所有 CH340 串口转换器在某批次车机上无法枚举,临时方案是在 init.rc 中添加:
on property:sys.usb.config=none
insmod /vendor/lib/modules/ch341.ko
chmod 0644 /sys/bus/usb-serial/drivers/ch341-uart/new_id
echo "1a86 7523" > /sys/bus/usb-serial/drivers/ch341-uart/new_id
其中
1a86 7523
是 CH340 的 VID/PID,
new_id
机制允许动态绑定未在内核中预注册的设备。
2.3 Framework 层:UsbManager 的权限模型与用户授权流程
UsbManager
是应用层接触 USB 的唯一入口,但它的行为受三重控制:
-
签名权限 :
android.permission.USB_PERMISSION是普通权限,但android.permission.MANAGE_USB是 signature|privileged 权限,仅 system app 或 platform 签名 app 可用。车载 HMI 应用若需免弹窗授权,必须申请后者,并在AndroidManifest.xml中声明:<uses-permission android:name="android.permission.MANAGE_USB" /> -
SELinux 策略 :即使有权限,SELinux 也会拦截
UsbDeviceConnection.open()。需在device/xxx/sepolicy/vendor/public.te中添加:allow hal_usb_default_service device:chr_file { read write open }; allow hal_usb_default_service sysfs_usb:file { read getattr }; -
用户授权 Dialog :对非 privileged app,
UsbManager.requestPermission()会触发系统 Dialog。但车载场景下,用户可能正开车,无法点击“允许”。我们的解法是:在UsbManager回调中监听ACTION_USB_ACCESSORY_ATTACHED,若检测到已知 VID/PID 的设备(如 CAN 适配器 VID=0x0403, PID=0x6001),则自动静默授权——前提是该设备已在/system/etc/usb_accessory_filter.xml中预置:<usb-accessory-filter> <usb-accessory vendor-id="1027" product-id="24577" /> </usb-accessory-filter>注意:
vendor-id和product-id必须为十进制,而非十六进制。这个 XML 文件会被UsbManager在启动时解析,匹配成功的设备将跳过 Dialog。
2.4 实操验证:五步定位 USB Host 失效根因
当
UsbManager.getDeviceList()
返回空时,按此顺序排查:
- 物理层 :用万用表测 USB Port 的 VBUS(Pin1)是否为 5V±5%,D+(Pin2)/D-(Pin3)对地电压是否在 0–3.3V 间浮动(未插设备时应为低电平)。
-
内核层
:
dmesg | grep -i "usb\|dwc3",查看是否有usbcore: registered new interface driver日志。若无,说明 Host Controller 未初始化。 -
设备节点
:
ls -l /dev/bus/usb/,正常应有001/001等目录。若无,检查CONFIG_USB_DEVICEFS是否启用。 -
驱动加载
:
lsusb -v(需 root),查看设备是否被内核识别。若显示ID 0000:0000,说明 VID/PID 未被任何 driver 绑定。 -
Framework 层
:
adb shell dumpsys usb,检查UsbService状态。若mUsbManager为 null,说明UsbService未启动,需检查SystemServer中UsbService的启动逻辑是否被 OEM 注释。
提示:车载项目调试时,务必使用
adb root+adb remount获取 root 权限,否则dmesg和lsusb均不可用。但注意,量产固件通常禁用 adb root,此时需依赖串口 console 或预置 debug apk。
3. USB 串口通信:从驱动兼容到高可靠数据收发的实战方案
3.1 驱动兼容性矩阵:为什么你的 CH340 在 Android 上打不开?
USB 串口在 Android 上的兼容性,本质是 Linux kernel driver 与 userspace termios 配置的协同问题 。AOSP 官方仅支持 CDC ACM(如 FT232、CP2102),对 CH340、PL2303 等国产芯片支持极弱。原因在于:
-
CH340 驱动缺陷
:Linux 4.14+ 内核的
ch341.c存在 race condition,open()后立即write()可能失败。修复补丁直到 5.10 才合入主线。 -
PL2303 协议差异
:老版 PL2303(B/A 型)需
pl2303_bdriver,新版(HX/TA 型)需pl2303driver,但 Android 内核常只编译一种。 -
VID/PID 白名单缺失
:
drivers/usb/serial/ch341.c中硬编码了支持的 VID/PID,若你的设备 VID/PID 不在列表中(如0x1a86/0x7523),driver 会直接 ignore。
解决方案分三级:
-
Level 1(推荐) :选用 CDC ACM 兼容芯片,如 CP2102N(VID=0x10c4, PID=0xea60)、FT232RL(VID=0x0403, PID=0x6001)。它们在所有 Android 版本上均原生支持,无需额外 driver。
-
Level 2 :若必须用 CH340,升级内核至 5.10+,并在
drivers/usb/serial/ch341.c中添加你的 PID:static const struct usb_device_id id_table[] = { { USB_DEVICE(0x1a86, 0x7523) }, // CH340G { USB_DEVICE(0x1a86, 0x5523) }, // CH341 { } }; -
Level 3(应急) :在 userspace 用
libusb绕过 kernel driver,直接操作 USB endpoint。但需 root 权限,且无法使用标准SerialPortAPI。
3.2 UsbSerialDriver 架构:Firmata 与 Custom Protocol 的选型逻辑
Android 上主流 USB 串口库有二:Google 官方
UsbSerial
(基于
UsbManager
)和开源
usb-serial-for-android
(基于
libusb
)。二者选型取决于场景:
| 维度 |
UsbSerial
(Google)
|
usb-serial-for-android
|
|---|---|---|
| 权限要求 | 需用户授权,不需 root |
需
android.permission.USB_PERMISSION
,部分功能需 root
|
| 驱动依赖 | 依赖 kernel driver,仅支持 CDC ACM/FTDI/CH340 等 | 不依赖 kernel driver,支持任意 USB device class |
| 性能 | 高,直接走 kernel buffer | 中,userspace buffer + JNI copy |
| 稳定性 | 高,与 framework 深度集成 | 中,JNI 层易 crash,需 careful error handling |
| 适用场景 | 车载 HMI 读取 OBD-II 数据(标准协议) | 调试阶段快速验证非标 USB 设备 |
我们量产项目采用
UsbSerial
,因其稳定性压倒一切。关键代码如下:
UsbSerialDriver driver = drivers.get(0); // 从 UsbManager 获取
UsbSerialPort port = driver.getPorts().get(0);
port.open(connection); // connection 为 UsbDeviceConnection
port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
// 发送指令(OBD-II AT 命令)
byte[] cmd = "ATZ\r\n".getBytes();
port.write(cmd, 1000);
// 接收响应(带超时)
byte[] buffer = new byte[1024];
int len = port.read(buffer, 1000); // 1000ms timeout
注意:
setParameters()必须在open()后调用,且read()的 timeout 参数单位为毫秒,非微秒。实测中,若 timeout 设为 0,read()会永久阻塞,导致 UI 线程卡死。
3.3 高可靠数据收发:解决粘包、丢包与乱序的三重加固
车载串口通信(如 OBD-II)对可靠性要求极高,单帧错误可能导致误报故障码。我们采用三层加固:
-
Layer 1:硬件流控 :在
setParameters()后启用 RTS/CTS:port.setRTS(true); port.setCTS(true);这要求串口转换器硬件支持流控,否则无效。
-
Layer 2:协议层校验 :OBD-II 帧格式为
7E 00 00 00 00 00 00 00(Header)+ Data + CRC。我们在read()后增加 CRC 校验:private boolean verifyCRC(byte[] data, int len) { int crc = 0; for (int i = 0; i < len - 1; i++) { crc ^= data[i]; } return crc == data[len - 1]; } -
Layer 3:应用层重传 :对关键命令(如
01 0C读发动机转速),实现超时重传(最多 3 次)与 ACK 机制。发送后启动Handler.postDelayed(),若 200ms 内未收到响应,则重发。
最终实测,在 115200bps 下,连续 10 小时通信丢包率 < 0.001%,满足 ASIL-B 要求。
3.4 常见问题速查表:从“无法打开”到“数据错乱”的根因与解法
| 现象 | 根因 | 解法 | 验证命令 |
|---|---|---|---|
UsbSerialPort.open()
抛
IOException: Connection refused
| kernel driver 未加载或 VID/PID 不匹配 |
lsusb -v
查看 device descriptor,确认 bInterfaceClass=2(CDC ACM)
|
lsusb -v | grep -A 5 "idVendor|idProduct"
|
read()
返回 0 字节
| 串口转换器未供电或 RX 线断开 | 用示波器测 RX 引脚是否有信号 |
stty -F /dev/ttyUSB0 -echo
(需 root)
|
数据乱码(如
0x00
变
0xFF
)
| termios 配置错误:stop bits 或 parity 不匹配 |
port.setParameters(baud, dataBits, STOPBITS_1, PARITY_NONE)
|
stty -F /dev/ttyUSB0
|
| 接收数据粘连(多帧合并) | 应用层未按协议边界解析 |
在
read()
后按协议 Header(如
7E
)分割帧
|
hexdump -C /dev/ttyUSB0 | head -20
|
write()
后无响应
| 未启用硬件流控,发送缓冲区溢出 |
port.setRTS(true)
+
port.setCTS(true)
|
cat /proc/tty/driver/usbserial
|
实操心得:车载项目调试时,务必用
screen /dev/ttyUSB0 115200(需 root)在串口 console 直接测试,绕过 Android app 层,快速定位是硬件问题还是软件问题。
4. USB-CAN 通信:从内核 CAN 驱动到车载诊断协议栈的落地实践
4.1 USB-CAN 的本质:它不是“USB 串口”,而是“USB 网络接口”
这是车载开发者最容易犯的认知错误。USB-CAN 适配器(如 PEAK PCAN-USB、Vector VN1630)在 Linux 中被识别为
CAN network interface
(如
can0
),而非串口设备(
ttyUSB0
)。其工作原理是:USB 设备固件将 CAN 帧封装为 USB bulk transfer,host 端 driver(如
peak_usb.ko
)解包后注入 kernel CAN subsystem,最终映射为 netdevice。这意味着:
-
你不能用
UsbSerialPort读写,而要用SocketCANAPI; -
ifconfig can0 up是必需步骤,否则can-utils工具无法工作; -
数据收发走的是
AF_CANsocket,而非AF_UNIX或AF_INET。
在 Android 上,
SocketCAN
支持需内核启用
CONFIG_CAN=y
,
CONFIG_CAN_RAW=y
,
CONFIG_CAN_DEV=y
,并编译
can-dev.ko
。我们曾因
CONFIG_CAN_DEV
未启用,导致
ip link add dev can0 type can
命令失败。
4.2 Kernel Driver 选型:PEAK vs. SocketCAN vs. Custom
车载项目常用三种 USB-CAN driver:
-
PEAK Driver
:闭源,需
pcan_usb.ko,支持 PEAK 全系设备。优势是稳定性高,劣势是无法定制。 -
SocketCAN Generic Driver
:开源,
usb_8dev.ko、ems_usb.ko等,支持多品牌。但需确认你的设备 VID/PID 是否在 driver 白名单中。 -
Custom Driver
:针对 OEM 自研 CAN 适配器,需从零编写
usb_can_driver.c,实现usb_probe()、usb_submit_urb()、can_send()等函数。
我们选择 PEAK Driver,因其通过了 ISO 11898-1 认证。加载流程如下:
# 加载 driver
insmod /vendor/lib/modules/pcan_usb.ko
# 创建 can0 interface
ip link add dev can0 type can bitrate 500000
ip link set can0 up
# 查看状态
ip -details -statistics link show can0
注意:
bitrate必须与 ECU 的 CAN 波特率严格一致,否则无法通信。车载常用 500kbps(动力总成)或 125kbps(车身网络)。
4.3 Android SocketCAN 封装:JNI 层的最小可行实现
Android NDK 未提供
AF_CAN
socket 的 Java 封装,需自行 JNI 实现。核心是
socket(PF_CAN, SOCK_RAW, CAN_RAW)
和
bind()
。关键代码:
// can_jni.c
JNIEXPORT jint JNICALL Java_com_example_CanService_openCanSocket(JNIEnv *env, jobject obj) {
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) return -1;
struct sockaddr_can addr;
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_index;
bind(s, (struct sockaddr*)&addr, sizeof(addr));
return s;
}
JNIEXPORT jint JNICALL Java_com_example_CanService_readCanFrame(JNIEnv *env, jobject obj, jint sock, jbyteArray buf) {
struct can_frame frame;
int len = recv(sock, &frame, sizeof(frame), MSG_DONTWAIT);
if (len < 0) return -1;
// copy frame.data to jbyteArray
(*env)->SetByteArrayRegion(env, buf, 0, frame.can_dlc, frame.data);
return frame.can_dlc;
}
Java 层调用:
int sock = openCanSocket(); // JNI call
byte[] data = new byte[8];
int dlc = readCanFrame(sock, data); // 读取 CAN 数据
提示:
recv()必须加MSG_DONTWAITflag,否则会阻塞。车载应用需实时性,不能容忍阻塞。
4.4 车载诊断协议栈:UDS over CAN 的帧组装与解析
USB-CAN 的终极目标是 UDS(Unified Diagnostic Services)。UDS 帧格式为:
Byte 0: PCI (Protocol Control Information) — 0x10 (single frame) or 0x21 (first frame)
Byte 1: Service ID (e.g., 0x22 for ReadDataByIdentifier)
Bytes 2+: Data
我们封装了一个
UdsClient
类,关键方法:
public void readDtc() {
// UDS 0x19 0x02: Read DTC Information
byte[] req = {0x19, 0x02};
sendCanFrame(0x7E0, req); // Target ECU ID
// Wait for response on 0x7E8
byte[] resp = receiveCanFrame(0x7E8, 1000);
if (resp[1] == (byte)0x59) { // Positive response
parseDtc(resp);
}
}
sendCanFrame()
将
can_frame
结构体填充后
send()
,
receiveCanFrame()
则
recv()
后按
can_id
过滤。实测中,ECU 响应延迟在 50–200ms 间,因此 timeout 设为 1000ms 是安全值。
4.5 常见问题与车载特化处理
-
问题:CAN 总线错误帧过多
根因:终端电阻缺失(应为 120Ω)或线缆过长(> 40m)。解法:用万用表测 CAN_H/CAN_L 间电阻,若为 60Ω,说明两端终端电阻均存在。 -
问题:UDS 响应超时
根因:ECU 未唤醒或诊断地址错误。解法:发送0x10 0x03(Default Session)唤醒 ECU,再发业务请求。 -
问题:Android 系统休眠导致 CAN 中断
根因:USB Host Controller 在 suspend 时断电。解法:在AndroidManifest.xml中添加android:keepScreenOn="true",并申请PowerManager.WakeLock。
实操心得:车载 CAN 测试必须用专业工具(如 Vector CANoe)先验证物理层和链路层,再切入 Android。我们曾花 3 天排查“UDS 无响应”,最后发现是 CANoe 的波特率设置为 250kbps,而车机为 500kbps。
5. HID 设备接入:从键盘鼠标到自定义 HID 的事件捕获与注入
5.1 Android HID 架构:Input Subsystem 与 InputManager 的分工
Android 的 HID 支持分为两级:
-
Kernel Level
:
drivers/hid/hid-core.c解析 HID Report Descriptor,生成input_event结构体,通过input_dev注册到 input subsystem。 -
Framework Level
:
InputManagerService从/dev/input/eventX读取input_event,分发给WindowManager或InputDispatcher。
关键点在于:
并非所有 HID 设备都能被 Android 识别
。只有符合 HID Usage Table 规范(如 Keyboard: 0x06 0x00, Mouse: 0x06 0x01)的设备,才会被
hid-generic.ko
driver 正确解析。而车载自定义 HID(如方向盘音量旋钮)常使用 Vendor-defined Usage Page(0xFF00),需 custom driver。
5.2 标准 HID 设备:键盘与鼠标的免驱接入
对标准 HID 键盘/鼠标,Android 开箱即用。但需注意:
-
权限 :
android.permission.INJECT_EVENTS是 signature|privileged 权限,仅 system app 可用。普通 app 无法模拟按键。 -
事件过滤 :
InputManager默认将 HID 事件分发给 foreground activity。若需全局捕获(如方向盘按键),需在AndroidManifest.xml中声明:<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:directBootAware="true"> <receiver android:name=".HidReceiver"> <intent-filter android:priority="1000"> <action android:name="android.intent.action.INPUT_EVENT" /> </intent-filter> </receiver> </application> -
事件解析 :
KeyEvent的getKeyCode()对应 HID Usage ID。例如,HID Usage 0x23(Volume Up)映射为KeyEvent.KEYCODE_VOLUME_UP。可在frameworks/base/core/java/android/view/KeyEvent.java中查表。
5.3 自定义 HID 设备:Report Descriptor 解析与 Vendor Usage 处理
车载自定义 HID(如 AC6328A2 方向盘按键)的 Report Descriptor 常含 Vendor-defined fields。例如:
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x09, 0x01, // Usage (0x01)
0x81, 0x02, // Input (Data,Var,Abs)
0xC0 // End Collection
此 descriptor 定义了 8-bit 的 Vendor Usage 输入。Android kernel 默认将其映射为
EV_MSC
(Miscellaneous)事件,而非
EV_KEY
。解法是在
drivers/hid/hid-ids.h
中添加 VID/PID,并在
drivers/hid/hid-core.c
的
hid_input_report()
中手动解析:
if (hid->vendor == 0x1234 && hid->product == 0x5678) {
// Parse vendor report
input_event(input, EV_KEY, KEY_VOLUMEUP, data[0] & 0x01);
input_event(input, EV_KEY, KEY_VOLUMEDOWN, (data[0] >> 1) & 0x01);
}
5.4 HID 事件捕获:InputReader 与 InputListener 的深度定制
若需捕获原始 HID 报文(如获取旋钮绝对角度),需 bypass
InputManager
,直接读
/dev/input/eventX
。步骤:
-
getevent -p查找 HID 设备对应的 event node(如/dev/input/event2)。 -
chmod 0666 /dev/input/event2(需 root)。 -
Java 层用
FileInputStream读取input_event结构体:
FileInputStream fis = new FileInputStream("/dev/input/event2");
byte[] buf = new byte[24]; // input_event is 24 bytes
fis.read(buf);
long tv_sec = ByteBuffer.wrap(buf, 0, 8).order(ByteOrder.LITTLE_ENDIAN).getLong();
short type = ByteBuffer.wrap(buf, 16, 2).order(ByteOrder.LITTLE_ENDIAN).getShort();
short code = ByteBuffer.wrap(buf, 18, 2).order(ByteOrder.LITTLE_ENDIAN).getShort();
int value = ByteBuffer.wrap(buf, 20, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
type=EV_MSC
,
code=MSC_SCAN
,
value
即原始 HID 报文。我们用此法实现了方向盘旋钮的 0–100% 精确映射。
5.5 常见问题与规避策略
| 问题 | 原因 | 解决方案 |
|---|---|---|
| HID 设备插入后无反应 |
kernel 未加载
hid-generic.ko
或
hid-logitech-dj.ko
|
lsmod | grep hid
,缺失则
insmod
|
| 按键事件被系统拦截(如 Home 键) |
InputManager
优先处理系统 key
|
在
Activity.onKeyDown()
中
return true
拦截
|
| 自定义 HID 报文解析错误 | Report Descriptor 中 Logical Min/Max 设置错误 |
用
hid-desc
工具解析 descriptor,确认
Logical Minimum
与
Physical Minimum
一致
|
| 多 HID 设备冲突 |
/dev/input/eventX
节点分配混乱
|
用
getevent -p
固定设备 node,避免动态分配
|
提示:
hid-report-descriptor分析工具 v1.7 是必备利器,可可视化 descriptor 结构,避免手动解析出错。我们曾因一个0x95(Report Count)值设为 0x10 而非 0x08,导致 8-bit 数据被解析为 16-bit,引发按键错乱。
6. 系统 API 深度整合:UsbManager、InputManager 与 HAL 的协同调用
6.1 UsbManager 的高级用法:设备热插拔监听与批量管理
UsbManager
不仅用于单设备授权,还可监听全局 USB 事件:
// 注册广播接收器
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
registerReceiver(usbReceiver, filter);
// 在 BroadcastReceiver 中处理
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Log.d("USB", "Attached: " + device.getDeviceName());
// 自动连接已知设备
if (isKnownDevice(device)) {
connectDevice(device);
}
}
}
关键点:
UsbManager.EXTRA_DEVICE
在
ACTION_USB_DEVICE_ATTACHED
中有效,但
ACTION_USB_DEVICE_DETACHED
中为 null,需自行维护设备列表。
6.2 InputManager 的全局事件监听:突破 Activity 生命周期限制
车载 HMI 常需在后台服务中监听 HID 事件。
InputManager
提供
InputManager.InputDeviceListener
,但仅适用于 foreground activity。替代方案是
InputMonitor
(需
android.permission.INJECT_EVENTS
):
InputManager im = (InputManager) getSystemService(INPUT_SERVICE);
im.registerInputDeviceListener(new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
if (device.isExternal() && device.getSources() == InputDevice.SOURCE_KEYBOARD) {
// Handle external keyboard
}
}
}, null);
注意:
onInputDeviceAdded()在设备插入时触发,但InputDevice对象需getDevice()获取,否则为 null。
6.3 HAL 层定制:为 OEM USB 设备编写 vendor HAL
当标准
UsbManager
无法满足需求(如需要 USB 设备固件升级),需编写 vendor HAL。以 USB-CAN 为例:
-
HAL Interface
:
hardware/interfaces/usb/1.0/IUsbCan.hal -
HAL Implementation
:
vendor/xxx/hardware/usb/1.0/UsbCan.cpp -
Service
:
vendor/xxx/hardware/usb/1.0/UsbCanService.cpp
HAL 方法如
upgradeFirmware()
通过
ioctl()
调用 kernel driver 的
USBDEVFS_IOCTL
。Java 层通过
HidlSupport
调用:
IUsbCan usbCan = IUsbCan.getService();
usbCan.upgradeFirmware(firmwareBytes);
此方案将敏感操作(如固件烧录)隔离在 vendor partition,符合车规安全要求。
6.4 SELinux 策略编写:让 USB API 在受限环境中运行
车载 SELinux 通常为
enforcing
模式。
UsbManager
和
InputManager
的调用需对应策略:
# Allow UsbManager to access device nodes
allow
196



被折叠的 条评论
为什么被折叠?



