Android: 关于USB accessory & HID

最近一直在看关于Android USB accessory与HID相关的文档,发现一组很有意思的应用AccessoryDisplaySource与AccessoryDisplaySink这两个app。将AccessoryDisplaySource安装到Nexus 4手机中,将AccessoryDisplaySink安装到SAMSUNG A9000手机中,再将两个手机通过USB OTG线相连,那么A9000的USB接口将工作在HOST模式,而Nexus 4的USB接口将工作在SLAVE(accessory)模式。两个手机将通过USB线相连之后:

  • Accessory Display

1. AccessoryDisplaySink向系统申请权限,使USB接口工作在HOST模式:

2016_05_19_usb_host_permission

2. 点击确认之后,AccessoryDispalySource也会向系统申请权限,使USB接口工作在SLAVE(accessory)模式:

2016_05_19_usb_accesory_permission

3. 点击“OK”之后,AccessoryDisplaySource会去创建media coder,创建virtual display,使用opengl将图像绘制到virtual display上,再通过media coder将virtual display中的图像进行编码,最后将编码生成的数据通过USB接口发送给AccesoryDisplaySink:

2016_05_19_usb_accesory_working

4. AccessoryDisplaySink将接收的数据进行解码,显示到屏幕上:

  • 2016_05_19_usb_host_workingUSB accessory

请看相关的参考文档。

  • HID

AOAv2 allows accessories to register one or more USB Human Interface Devices (HID) with an Android device. This approach reverses the direction of communication for typical USB HID devices such as USB mice and keyboards. Normally, the HID device is a peripheral connected to a USB host (i.e. a personal computer), but in AOA the USB host can act as one or more input devices to a USB peripheral.

AccessoryDisplaySink除了显示AccessoryDisplaySource生成的图象之外,还会将显示区域的Touch事件回传给运行AccessoryDisplaySource的设备(Nexus 4):

AccessoryDisplaySink在建立USB连接时,会发送命令给Nexus4,让它创建HID Raw device

Generates basic Windows 7 compatible HID multitouch descriptors and reports that should be supported by recent versions of the Linux hid-multitouch driver.

frameworks/base/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java

public class SinkActivity extends Activity {
    // ...
    private void registerHid() {
        mLogger.log("Registering HID multitouch device.");

        mMultitouch = new UsbHid.Multitouch(MULTITOUCH_REPORT_ID, MULTITOUCH_MAX_CONTACTS,
                mSurfaceView.getWidth(), mSurfaceView.getHeight());

        mHidBuffer.clear();
        mMultitouch.generateDescriptor(mHidBuffer);
        mHidBuffer.flip();

        mLogger.log("HID descriptor size: " + mHidBuffer.limit());
        mLogger.log("HID report size: " + mMultitouch.getReportSize());

        final int maxPacketSize = mControlEndpoint.getMaxPacketSize();
        mLogger.log("Control endpoint max packet size: " + maxPacketSize);
        if (mMultitouch.getReportSize() > maxPacketSize) {
            mLogger.logError("HID report is too big for this accessory.");
            return;
        }

        int len = mAccessoryConnection.controlTransfer(
                UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
                UsbAccessoryConstants.ACCESSORY_REGISTER_HID,
                MULTITOUCH_DEVICE_ID, mHidBuffer.limit(), null, 0, 10000);
        if (len != 0) {
            mLogger.logError("Device rejected ACCESSORY_REGISTER_HID request.");
            return;
        }

        while (mHidBuffer.hasRemaining()) {
            int position = mHidBuffer.position();
            int count = Math.min(mHidBuffer.remaining(), maxPacketSize);
            len = mAccessoryConnection.controlTransfer(
                    UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
                    UsbAccessoryConstants.ACCESSORY_SET_HID_REPORT_DESC,
                    MULTITOUCH_DEVICE_ID, 0,
                    mHidBuffer.array(), position, count, 10000);
            if (len != count) {
                mLogger.logError("Device rejected ACCESSORY_SET_HID_REPORT_DESC request.");
                return;
            }
            mHidBuffer.position(position + count);
        }

        mLogger.log("HID device registered.");

        mMultitouchEnabled = true;
        if (mMultitouchContacts == null) {
            mMultitouchContacts = new UsbHid.Multitouch.Contact[MULTITOUCH_MAX_CONTACTS];
            for (int i = 0; i < MULTITOUCH_MAX_CONTACTS; i++) {
                mMultitouchContacts[i] = new UsbHid.Multitouch.Contact();
            }
        }
    }
    // ...
}

而https://android.googlesource.com/kernel/msm/+/android-msm-mako-3.4-lollipop-mr1.1/drivers/usb/gadget/f_accessory.c接收到相关的请求之后,就会去创建HID设备,处理HID事件。

这里,Nexus 4并没有创建HID设备,也没能够处理HID事件,BUG?

NOTE: 找了一台华为的手机做测试,打开系统设置->开发人员选项->显示触摸操作与指针位置之后,手指在A9000上在显示区域上滑动,华为手机上可以看到确实有上报输入事件:

shell@HWEVA:/ $ getevent -lt                                                   
add device 1: /dev/input/event6
  name:     ""
...
[    2389.603705] /dev/input/event6: EV_ABS       ABS_MT_TRACKING_ID   00000000            
[    2389.603705] /dev/input/event6: EV_ABS       ABS_MT_POSITION_X    00000111            
[    2389.603705] /dev/input/event6: EV_ABS       ABS_MT_POSITION_Y    00000100            
[    2389.603705] /dev/input/event6: EV_KEY       BTN_TOUCH            DOWN                
[    2389.603705] /dev/input/event6: EV_ABS       ABS_X                00000111            
[    2389.603705] /dev/input/event6: EV_ABS       ABS_Y                00000100            
[    2389.603705] /dev/input/event6: EV_SYN       SYN_REPORT           00000000            
[    2389.663269] /dev/input/event6: EV_ABS       ABS_MT_POSITION_X    00000112            
[    2389.663269] /dev/input/event6: EV_ABS       ABS_MT_POSITION_Y    000000ff            
[    2389.663269] /dev/input/event6: EV_ABS       ABS_X                00000112            
[    2389.663269] /dev/input/event6: EV_ABS       ABS_Y                000000ff            
[    2389.663269] /dev/input/event6: EV_SYN       SYN_REPORT           00000000            
[    2389.663893] /dev/input/event6: EV_ABS       ABS_MT_POSITION_X    00000114            
[    2389.663893] /dev/input/event6: EV_ABS       ABS_X                00000114            
[    2389.663893] /dev/input/event6: EV_SYN       SYN_REPORT           00000000            
[    2389.681262] /dev/input/event6: EV_ABS       ABS_MT_POSITION_X    00000118

Nexus 4的kernel是有源代码的, 可以从https://android.googlesource.com/kernel/msm/下载,对于android-5.1.1_r15(Nexus 4 最后一个正式版LMY48T 对应的tag)来说,kernel的commit ID为dffc258

mako: update prebuilt kernel [DO NOT MERGE]

dffc258 Revert "Update to 4.7 toolchain to match previous config"
063389c ipv4: Missing sk_nulls_node_init() in ping_unhash().
7555e42 Update to 4.7 toolchain to match previous config

Linux version 3.4.0-perf-gdffc258 (android-build@vpee9.mtv.corp.google.com)
(gcc version 4.6.x-google 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT Wed Jul 8 23:25:22 UTC 2015

Bug: 20770158

Change-Id: I6caa209c4dd0e708817594c1bf42a3c02b24869a
Signed-off-by: Patrick Tjin <pattjin@google.com>

感兴趣的话可以下载代码看看问题出在哪里。

  • 调试相关

由于两个手机的USB口都被占用了,我们无法通过adb命令从USB接口进行debug。这时我们可以通过如下命令使adbd接受网络连接(假设手机接入无线网络,IP地址为10.0.0.7):

$ adb tcpip 5555
$ adb connect 10.0.0.7
connected to 10.0.0.7:5555
$ adb devices
List of devices attached 
01bf6cad580eeebd        device
10.0.0.7:5555   device

我们可以看到手机既可以通过USB进行通信,也可以通过TCP/IP进行通信。

当两个手机通过USB OTG线进行连接之后,Nexus 4 USB function的配置就发生的改变:

$ adb shell getprop | grep usb
[persist.sys.usb.config]: [mtp,adb]
[sys.usb.config]: [accessory,adb]
[sys.usb.state]: [accessory,adb]
  • 相关的参考文档
  1. https://android.googlesource.com/platform/frameworks/base/+/android-5.1.1_r15/tests/AccessoryDisplay/README
  2. https://developer.android.com/guide/topics/connectivity/usb/index.html
  3. https://developer.android.com/guide/topics/connectivity/usb/accessory.html
  4. https://developer.android.com/guide/topics/connectivity/usb/host.html
  5. http://source.android.com/devices/accessories/aoa2.html#hid-support

发表评论

电子邮件地址不会被公开。 必填项已用*标注