Android到Android的USB AOA协议通讯的实现

研究android2android aoa通讯时,在网上查询了很多资料,这些资料对Accessory模式的描述,在研究过程中造成了很大的困扰,故此先对基本信息进行介绍。

1.1 Host模式与Accessory模式的区别

1.png

1.2 Accessory端的PID/VID

    VID  固定为Google的官方VID – 0x18D1

    PID 在不同的模式下定义如下:

        ●         0x2D00 - accessory

        ●          0x2D01 - accessory + adb

        ●          0x2D02 - audio

        ●          0x2D03 - audio + adb

        ●          0x2D04 - accessory + audio

        ●          0x2D05 - accessory + audio + adb

1.3 Android USB Accessory设备和Android Host设备两者枚举识别工作过程

流程图.PNG

枚举过程如图所示

1. 首先USB Accessory设备发起USB控制传输进行正常的USB设备枚举,获取设备描述符和配置描述符等信息。

此时大部分Android设备上报的还只是普通的HID或MTP设备

2. 接下来USB Accessory设备,根据枚举的PID/VID,向对应的USB设备,发起Vendor类型,request值为51(0x33)的控制传输命令(ACCESSORY_GET_PROTOCOL),

看看该Android设备是否支持USB Accessory功能,如果支持的话会返回所支持的AOA协议版本(1或2)。

3. USB Accessory判断到该Android设备支持Accessory功能后,

发起request值为52(0x34)的控制传输命令(ACCESSORY_SEND_STRING),

并把该Accessory设备的相关信息(包括厂家,序列号等)告知Android设备;

4. 最终,USB Accessory设备发起request值为53(0x35)的控制传输命令(ACCESSORY_START),

通知Android设备切换到Accessory功能模式开始工作。

至此,Android Accessory端与Host端的识别工作完成,接下来是通讯:Accessory端使用块传输,Host端使用输入输出流,具体代码如下。

1.4 Android USB Accessory设备和Android设备两者枚举识别工作过程的代码实现

Accessory端
1.设备枚举

    public final String myUsbDevices = "1234/1234";
    public static final String[]aoaPidVid = {"2D00/18D1","2D01/18D1","2D02/18D1","2D03/18D1","2D04/18D1","2D05/18D1"};
    /**
     * 枚举usb设备,并修改为Accessory模式</br>
     * 本例中只考虑连接一个Accessory设备的情况
     * **/
    private UsbDevice findDevice(Context context, UsbManager mUsbManager){
        final HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
        Log.i(TAG,"initAccessory: deviceList=" + deviceList.size());
        if (deviceList == null || deviceList.size() == 0) {
            Log.i(TAG,"initAccessory: Not found usb device");
            return null;
        }

        for (UsbDevice dev:deviceList.values()) {
            String pid = Integer.toHexString(dev.getProductId());
            String vid = Integer.toHexString(dev.getVendorId());
            String pidVid = pid+"/"+vid;
            Log.i(TAG,"initAccessory: find usb device["+pidVid+"]");
            //判断枚举的usb设备是否目标设备
            if(myUsbDevices.equals(pidVid)){
                while (!mUsbManager.hasPermission(dev)) {
                    Log.i(TAG,"initAccessory: Do not have permission on device=" + dev.getProductName());
                    Intent intent = new Intent(IAoaConst.ACTION_USB_PERMISSION);
                    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                    Log.i(TAG,"initAccessory: Trying to get permissions with pendingIntent=" + pendingIntent);
                    mUsbManager.requestPermission(dev, pendingIntent);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(initAccessory(mUsbManager,dev)){
                    Log.i(TAG,"initAccessory: Init usb accessory success");
                    return dev;
                }
            }else if(isArrayContainStr(aoaPidVid,pidVid)){//判断设备是否为Accessory设备
                return dev;
            }

        }
        return null;
    }

2.初始化Accessory:
查询Android Host端是否支持AOA协议,向Android Host端发送AndroidAccessory端的厂商/序列号等内容,通知Android Host端切换到Accessory模式

/**
         * 配件发送序号52的USB请求报文,通过Index字段携带配件自身信息,包括制造商、型号、版本、设备描述、序 列号URI等。手机根据这些信息启动响应的APP
         * 配件发送序号53的USB请求报文,切换USB模式,主要是根据切换的vendorID和productID
         * 重新枚举USB设备,准备建立AOA数据通道
         */
private boolean initAccessory(UsbManager mUsbManager,final UsbDevice device) {
        Log.i(TAG,"initAccessory: device=[name=" + device.getDeviceName() +
                ", manufacturerName=" + device.getManufacturerName() +
                ", productName=" + device.getProductName() +
                ", deviceId=" + device.getDeviceId() +
                ", productId=" + device.getProductId() +
                ", deviceProtocol=" + device.getDeviceProtocol() + "]");
        //无拔出usb的动作,直接返回成功,不重新init accessory
        if(isDetached){
            Log.i(TAG,"initAccessory: have not detach usb,return true");
            return true;
        }
        connection = mUsbManager.openDevice(device);
        Log.i(TAG,"initAccessory: conneciton=" + connection);
        if (connection == null) {
            return false;
        }

        
        //Android Host端是否支持AOA协议
        int result = getProtocol(connection);
        Log.i(TAG,"controlTransfer(51)accessoryVersion = "+result);
        if(result==1||result==2){
            boolean res = initStringControlTransfer(connection, 0, IAoaConst.USB_AOA_MANUFACTURER); // MANUFACTURER
            res = res&&initStringControlTransfer(connection, 1, IAoaConst.USB_AOA_MODEL); // MODEL
            res = res&&initStringControlTransfer(connection, 2, IAoaConst.USB_AOA_DESCRIPTION); // DESCRIPTION
            res = res&&initStringControlTransfer(connection, 3, IAoaConst.USB_AOA_VERSION); // VERSION
            res = res&&initStringControlTransfer(connection, 4, IAoaConst.USB_AOA_URI); // URI
            res = res&&initStringControlTransfer(connection, 5, IAoaConst.USB_AOA_SERIAL); // SERIAL

            connection.controlTransfer(0x40, 53, 0, 0, new byte[]{}, 0, IAoaConst.INIT_USB_ACCESSORY_TIMEOUT);

            connection.close();

            return res;
        }else{
            Log.i(TAG,"Host not support accessory protocol ["+result+"]");
            return false;
        }
    }

3.使用块传输进行通信

Host端
1.枚举accessory设备

public void startHost(Context context,final IUSBCallback usbCallback){
        Log.i(TAG,"startHost enter.");
        final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        registerUsbReceiver(context,usbCallback);
        findUsbAccessory(context,new IFindAccessoryCallback() {
            @Override
            public void findAccessory(final UsbAccessory accessory) {
                if (accessory==null) {
                    bUsbAttach = false;
                    Log.w(TAG,"no accessory found");
                    usbCallback.disconnectd(IAoaErrCode.ERR_NO_ACCESSORY_FIND,"no accessory found");
                }else {
                    if(openAccessory(usbManager,accessory)){
                        //启动读取数据线程
                        Log.d(TAG, "mReadThread is start ");
                        if(mReadThread==null||!mReadThread.isAlive()){
                            mReadThread = new ReadThread(usbCallback);
                            mReadThread.start();
                        }
                        bUsbAttach = true;
                        Log.i(TAG,"Open accessory success");
                        usbCallback.connected();
                    }else{
                        bUsbAttach = false;
                        Log.i(TAG,"Open accessory fail");
                        usbCallback.disconnectd(IAoaErrCode.ERR_OPEN_ACCESSORY_FAIL,"Open accessory fail");
                    }
                }
            }
        });
    }

private void findUsbAccessory(final Context context,final IFindAccessoryCallback callback){
        if(this.findUsbAccessoryThread==null||!this.findUsbAccessoryThread.isAlive()){
            this.findUsbAccessoryThread = new Thread(){
                @Override
                public void run(){
                    Log.i(TAG,"FindUsbAccessoryThread enter...");
                    findUsbAccessoryFlag = true;
                    while(findUsbAccessoryFlag){

                        try {
                            sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
//                          UsbAccessory accessory = (UsbAccessory) getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                        final UsbAccessory[] accessoryList = usbManager.getAccessoryList();
                        if (accessoryList != null && accessoryList.length > 0) {
                            for(UsbAccessory usbAccessory : accessoryList){
                                if(isUsbAccessory(usbAccessory)){
                                    while(!usbManager.hasPermission(usbAccessory)){
                                        Intent intent = new Intent(IAoaConst.ACTION_USB_PERMISSION);
                                        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                                        Log.i(TAG,"initDevice: Trying to get permissions with pendingIntent=" + pendingIntent);
                                        usbManager.requestPermission(usbAccessory, pendingIntent);
                                        try {
                                            Thread.sleep(1000);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                    Log.i(TAG,"FindUsbAccessoryThread find usbAccessory["+usbAccessory+"]"+usbManager.getDeviceList().size());
                                    callback.findAccessory(usbAccessory);
                                    findUsbAccessoryFlag = false;
                                }
                            }
                        }
                    }
                    Log.i(TAG,"FindUsbAccessoryThread exit...");
                }
            };
            this.findUsbAccessoryThread.start();
        }
    }

private boolean openAccessory(UsbManager usbManager,UsbAccessory accessory) {
        try{
            if(this.fileDescriptor==null){

                this.fileDescriptor = usbManager.openAccessory(accessory);
            }

            if (this.fileDescriptor != null) {

                FileDescriptor fd = fileDescriptor.getFileDescriptor();
                this.fileInputStream = new FileInputStream(fd);
                this.fileOutStream = new FileOutputStream(fd);
                this.bufferedOutputStream = new BufferedOutputStream(fileOutStream);
                if(this.fileInputStream==null||this.bufferedOutputStream==null){
                    return false;
                }else return true;
            } else {
                return false;
            }
        }catch (Exception e){
            Log.w(TAG, "openAccessory exception:"+e.getMessage(),e);
            return false;
        }
    }

2.使用取到的BufferedOutputStream和FileInputStream进行通信

至此Android至Android的Usb aoa通信已完成,以下是完整demo工程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351