Android 车载 AOA 开发笔记:两个 Android 设备通过 USB Host 与 Accessory Mode 双向通信
Android Open Accessory(AOA)可以让两个 Android 设备通过 USB 建立双向通信,其中一个 Android 作为 USB Host,另一个 Android 作为 USB Device,并由 Host 主动将 Device 切换到 Accessory Mode。典型链路:Android Host → UsbManager → AOA 51/52/53 → Android Device → Accessory Mode → Bulk IN/OUT → 双向数据通信。Host 端主要使用 UsbManager + UsbDevice + UsbDeviceConnection + UsbInterface + UsbEndpoint,Device 端主要使用 UsbManager + UsbAccessory + ParcelFileDescriptor。
AOA 通信流程
完整流程:Host 枚举 Android Device → 获取 UsbDevice Permission → openDevice → GET_PROTOCOL 51 → SEND_STRING 52 → ACCESSORY_START 53 → Device USB 断开并重新枚举 → Host 发现 18D1:2D00/2D01 → 获取新的 UsbDevice Permission → claimInterface → Bulk IN/OUT → Device 获取 UsbAccessory → openAccessory → InputStream/OutputStream。
可以简单记录为:51:查询是否支持 AOA,52:发送 Host 身份信息,53:要求 Device 切换到 Accessory Mode。
Host 与 Device
Host Android 使用:UsbDevice + UsbDeviceConnection + controlTransfer() + UsbEndpoint + bulkTransfer()。
Device Android 使用:UsbAccessory + openAccessory() + ParcelFileDescriptor + FileInputStream/FileOutputStream。
因此:Host 看到 UsbDevice,Device 看到 UsbAccessory。
Host 条件
Host Android 需要声明:
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
硬件接口必须实际工作在 USB Host Role,另一台 Android 必须工作在 USB Device Role。两个 Android 使用 USB-C 连接不代表角色一定正确,实际还受到 USB Controller + Type-C Role + OTG + Cable + System USB Configuration 影响。
GET_PROTOCOL
Host 首先发送 Vendor Control Request:
bmRequestType = USB_DIR_IN | USB_TYPE_VENDOR,bRequest = 51,wValue = 0,wIndex = 0,wLength = 2。
核心代码:
byte[] protocol = new byte[2];
int length = connection.controlTransfer(UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR, 51, 0, 0, protocol, 2, 1000);
int version = length == 2 ? (protocol[0] & 0xFF) | ((protocol[1] & 0xFF) << 8) : -1;
version > 0 才继续 AOA 握手。如果 Request 51 失败,不应该继续发送 52 和 53。
SEND_STRING
Host 使用 Request 52 向 Device 发送 Accessory Identification。
String ID:0:Manufacturer,1:Model,2:Description,3:Version,4:URI,5:Serial。
发送一个字符串的核心逻辑:
byte[] data = Arrays.copyOf("AndroidHost".getBytes(StandardCharsets.UTF_8), "AndroidHost".getBytes(StandardCharsets.UTF_8).length + 1);
connection.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, 52, 0, 0, data, data.length, 1000);
实际依次发送:
byte[] manufacturer = Arrays.copyOf("AndroidHost".getBytes(StandardCharsets.UTF_8), "AndroidHost".getBytes(StandardCharsets.UTF_8).length + 1);
connection.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, 52, 0, 0, manufacturer, manufacturer.length, 1000);
byte[] model = Arrays.copyOf("VehicleHost".getBytes(StandardCharsets.UTF_8), "VehicleHost".getBytes(StandardCharsets.UTF_8).length + 1);
connection.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, 52, 0, 1, model, model.length, 1000);
通常真正影响 Device 应用匹配的主要是:Manufacturer + Model。
ACCESSORY_START
发送完 Identification 后执行:
int result = connection.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, 53, 0, 0, null, 0, 1000);
Request 53 成功后,对端 Android 会退出当前 USB Device 状态并重新枚举。
因此:
Request 53 成功 ≠ 可以立即 Bulk Transfer
正确流程:Request 53 → close 旧 UsbDeviceConnection → 等待 Device Detached → 等待新的 USB_DEVICE_ATTACHED → 获取新的 UsbDevice。
AOA VID/PID
Device 进入 Accessory Mode 后,Host 通常会看到:
VID = 0x18D1
常见 PID:
0x2D00:Accessory
0x2D01:Accessory + ADB
Host 判断:
boolean aoa = device.getVendorId() == 0x18D1 && (device.getProductId() == 0x2D00 || device.getProductId() == 0x2D01);
Request 53 前的 UsbDevice 和重新枚举后的 UsbDevice 不是同一个连接状态,旧 Connection、Interface、Endpoint 都不能继续使用。
Host USB Permission
Host 操作 UsbDevice 前需要:
boolean granted = usbManager.hasPermission(device);
没有权限:
PendingIntent intent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION).setPackage(context.getPackageName()), PendingIntent.FLAG_IMMUTABLE);
usbManager.requestPermission(device, intent);
有权限后:
UsbDeviceConnection connection = usbManager.openDevice(device);
Device 重新枚举成 AOA Device 后,需要针对新的 UsbDevice 再检查一次 Permission。
Host 查找 AOA Interface
AOA 数据 Interface 应同时包含 Bulk IN + Bulk OUT。不要默认 getInterface(0) 永远正确,尤其 2D01 还可能同时存在 ADB Interface。
核心遍历逻辑:
for (int i = 0; i < device.getInterfaceCount(); i++) { UsbInterface itf = device.getInterface(i); boolean in = false, out = false; for (int j = 0; j < itf.getEndpointCount(); j++) { UsbEndpoint ep = itf.getEndpoint(j); if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK && ep.getDirection() == UsbConstants.USB_DIR_IN) in = true; if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK && ep.getDirection() == UsbConstants.USB_DIR_OUT) out = true; } if (in && out) { accessoryInterface = itf; break; } }
找到后:
connection.claimInterface(accessoryInterface, true);
Host 查找 Endpoint
for (int i = 0; i < accessoryInterface.getEndpointCount(); i++) { UsbEndpoint ep = accessoryInterface.getEndpoint(i); if (ep.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) continue; if (ep.getDirection() == UsbConstants.USB_DIR_IN) endpointIn = ep; else if (ep.getDirection() == UsbConstants.USB_DIR_OUT) endpointOut = ep; }
方向始终站在 Host 角度:Bulk OUT:Host → Device,Bulk IN:Device → Host。
Host 发送数据
Host → Device:
byte[] data = {(byte) 0xAA, 0x55, 0x01, 0x02};
int length = connection.bulkTransfer(endpointOut, data, data.length, 1000);
length >= 0 表示 USB Transfer 成功,不代表 Device 业务逻辑已经处理完成。
Host 接收数据
Device → Host:
byte[] buffer = new byte[16384];
int length = connection.bulkTransfer(endpointIn, buffer, buffer.length, 1000);
byte[] data = length > 0 ? Arrays.copyOf(buffer, length) : null;
持续接收应该放在工作线程中,避免 Main Thread 阻塞。
Device Manifest
Device Android 声明:
<uses-feature android:name="android.hardware.usb.accessory" android:required="true" />
需要自动匹配 Accessory 时:
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" android:resource="@xml/accessory_filter" />
accessory_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="AndroidHost" model="VehicleHost" />
</resources>
这里必须与 Host Request 52 发送的:
Manufacturer = AndroidHost
Model = VehicleHost
保持一致。
Device 获取 UsbAccessory
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessories = usbManager.getAccessoryList();
UsbAccessory accessory = accessories != null && accessories.length > 0 ? accessories[0] : null;
可以读取:
getManufacturer() + getModel() + getDescription() + getVersion() + getUri() + getSerial()。
这些内容来自 Host 的 Request 52。
Device Permission
boolean granted = usbManager.hasPermission(accessory);
没有权限:
PendingIntent intent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION).setPackage(context.getPackageName()), PendingIntent.FLAG_IMMUTABLE);
usbManager.requestPermission(accessory, intent);
需要区分:Host 使用 UsbDevice Permission,Device 使用 UsbAccessory Permission。
Device 打开 Accessory
ParcelFileDescriptor descriptor = usbManager.openAccessory(accessory);
FileDescriptor fd = descriptor.getFileDescriptor();
FileInputStream inputStream = new FileInputStream(fd);
FileOutputStream outputStream = new FileOutputStream(fd);
Device 不需要操作:UsbInterface + UsbEndpoint + UsbDeviceConnection + bulkTransfer()。
Framework 已经把 Accessory 数据通道封装成 File Descriptor。
Device 接收 Host 数据
byte[] buffer = new byte[16384];
int length = inputStream.read(buffer);
byte[] data = length > 0 ? Arrays.copyOf(buffer, length) : null;
对应关系:
Host bulkTransfer(Bulk OUT) → Device FileInputStream.read()。
Device 发送数据
byte[] data = {0x11, 0x22, 0x33, 0x44};
outputStream.write(data);
outputStream.flush();
对应关系:
Device FileOutputStream.write() → Host bulkTransfer(Bulk IN)。
USB 断开
发生 USB Disconnect 后,两端旧对象都失效。
Host 重新获取:UsbDevice + UsbDeviceConnection + UsbInterface + UsbEndpoint。
Device 重新获取:UsbAccessory + ParcelFileDescriptor + FileInputStream + FileOutputStream。
不要在重新枚举后继续复用旧对象。