Android AOA链接(accessory & host)

2 篇文章 0 订阅

目录

AOA连接两台Android 设备

1. 配件端(手机)

1.1AndroidManifest 要求

1.2 与配件通信

1.3 终止通信

2. 主机端(车机)

2.1 API 概述

2.2 AndroidManifest 要求

2.3 启动Android设备(手机端)配件模式

2.4 枚举设备

2.5 获取与设备进行通信的权限

2.6 与设备通信

2.7 终止与设备通信

3. 参考链接:


AOA连接两台Android 设备

Android 开放配件 (AOA) 支持功能可让外部 USB 硬件(Android USB 配件, 车机)与处于配件模式下的 Android 设备(手机)进行交互。当某台 Android 设备(手机)处于配件模式时,所连接的配件(车机)会充当 USB 主机(为总线供电并列举设备),而 Android 设备(手机)则充当 USB 配件。

 

1. 配件端(手机)

 

1.1AndroidManifest 要求

  1. 因为并非所有 Android 设备被授权支持 USB accessory API,因此包含一个 <uses-feature> 元素,声明您的应用程序使用 android.hardware.usb.accessory 功能。

  2. 如果您正在使用附加库,请添加指定 com.android.future.usb.accessory 的 <uses-library> 元素。

  3. 如果您正在使用附加库,请将应用程序的最小 SDK 设置为 API Level 10,如果使用的是android.hardware.usb 包,则将其设置为12。

  4. 如果您希望应用程序在 USB 配件连接时收到通知,请在主要活动中为 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 意图指定 <intent-filter> 和 <meta-data> 元素对。<meta-data> 元素指向一个外部XML资源文件,它声明了您想检测的配件的标识信息。

在XML资源文件中,为要过滤的配件声明 <usb-accessory> 元素。每个 <usb-accessory> 可以具有以下属性:

  • manufacturer

  • model

  • version

将资源文件保存在 res/xml/ 目录中。资源文件名(不含 .xml 扩展名)必须与您在 <meta-data> 元素中指定的文件名相同。

1.1.1 AndroidManifest.xml

<uses-feature android:name="android.hardware.usb.accessory" />
...
​
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <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" />
</activity>
<activity android:name=".TestActivity"></activity>

1.1.2 res/xml/accessory_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory
        manufacturer="A B C"
        model="CarLink"
        version="1.0.0"/>
</resources>

 

1.2 与配件通信

使用 UsbManager 与配件通信,以获取文件描述符,您可以配置输入和输出流来读取和写入数据到描述符。 数据流表示配件的输入和输出批量端点。 您应该在另一个线程中建立设备和配件之间的通信,因而不会阻塞主UI线程。 以下示例展示如何打开配件进行通信:

UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;
​
...
​
private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
    mFileDescriptor = mUsbManager.openAccessory(mAccessory);
    if (mFileDescriptor != null) {
        FileDescriptor fd = mFileDescriptor.getFileDescriptor();
        mInputStream = new FileInputStream(fd);
        mOutputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}
​
//输入输出:
try {
    int length = mFileInputStream.read(mBytes);
    String text = new String(mBytes, 0, ret);
} catch (IOException e) {
    e.printStackTrace();
    break;
}
try {
    mFileOutputStream.write(mBytes);
} catch (IOException e) {
    e.printStackTrace();
    break;
}

 

1.3 终止通信

BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
​
        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
            UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
            if (accessory != null) {
             // call your method that cleans up and closes communication with accessory
                try {
                    if (mParcelFileDescriptor != null) {
                        mParcelFileDescriptor.close();
                    }
                    if (mFileInputStream != null) {
                        mFileInputStream.close();
                        mFileInputStream = null;
                    }
                    if (mFileOutputStream != null) {
                        mFileOutputStream.close();
                        mFileOutputStream = null;
                    }
                
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
};

2. 主机端(车机)

2.1 API 概述

在开始之前,理解需要使用的类是很重要的。下表描述了 android.hardware.usb 包里的 USB 主机 API 函数。

USB Host APIs

ClassDescription
UsbManage负责枚举和与连接的 USB 设备通讯
UsbDevice表示连接的 USB 设备并且包含访问其标识信息,接口和端点的方法
UsbInterface表示 USB 设备的接口,它定义了设备的一组功能。 设备可以具有一个或多个接口进行通信。
UsbEndpoint表示接口端点,它是该接口的通信通道。 接口可以有一个或多个端点,并且通常具有与设备进行双向通信的输入和输出端点。
UsbDeviceConnection表示与设备的连接,该设备在端点上传输数据。 该类允许您以同步方式或异步方式来回发送数据。
UsbRequest表示通过UsbDeviceConnection与设备通信的异步请求。
UsbConstants定义与Linux内核的linux / usb / ch9.h中的定义对应的USB常量。

在大多数情况下,与 USB 设备通讯时需要使用所有这些类(UsbRequest 只在异步方式通讯的时候需要)。通常,会获取一个 UsbManager 来检索所需的 UsbDevice。当获取到设备时,需要查找合适的 UsbInterface 与接口中用于通讯的UsbEndpoint。一旦获取到正确的端点,打开一个 UsbDeviceConnection与 USB 设备通讯。

 

2.2 AndroidManifest 要求

由于并非所有安卓设备被授权支持 USB host API 函数,因此需要包含一个 <uses-feature>元素来声明你的应用使用 android.hardware.usb.host功能。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.abc.carlink.car">
​
    <uses-feature android:name="android.hardware.usb.host" />
    ....

 

2.3 启动Android设备(手机端)配件模式

配件应尝试以配件模式启动设备,以确定设备是否支持该模式:

  1. 发送 51 控制请求(“获取协议”)以确定设备是否支持 Android 配件协议。如果设备支持协议,则返回一个非零数字,代表所支持的协议版本。该控制请求为端点 0 上的请求,具有以下特征:

    requestType:    USB_DIR_IN | USB_TYPE_VENDOR
    request:        51
    value:          0
    index:          0
    data:           protocol version number (16 bits little endian sent from the
                    device to the accessory)
  2. 如果设备返回所支持的协议版本,则向设备发送含标识字符串信息的控制请求。该信息让设备可以确定适合配件的应用(如果没有适合配件的应用,则向用户呈现一个网址)。该控制请求为端点 0 上的请求(适用每个字符串 ID),具有以下特征:

    requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
    request:        52
    value:          0
    index:          string ID
    data            zero terminated UTF8 string sent from accessory to device

    支持以下字符串 ID,并且每个字符串的最大值为 256 个字节(必须以零结束,以 \0 结尾)。

    manufacturer name:  0
    model name:         1
    description:        2
    version:            3
    URI:                4
    serial number:      5

     

  3. 发送控制请求,要求设备以配件模式启动。该控制请求为端点 0 上的请求,具有以下特征:

    requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
    request:        53
    value:          0
    index:          0
    data:           none       
    

 

    private static final int AOA_GET_PROTOCOL = 51;
    private static final int AOA_SEND_IDENT = 52;
    private static final int AOA_START_ACCESSORY = 53;
    private static final String AOA_MANUFACTURER = "ABC";
    private static final String AOA_MODEL_NAME = "CarLink";
    private static final String AOA_DESCRIPTION = "ABC AOA_DESCRIPTION";
    private static final String AOA_VERSION = "1.0.0";
    private static final String AOA_URI = "http://www.abc.com.cn/";
    private static final String AOA_SERIAL_NUMBER = "123456.";

    public boolean changeToAccessoryMode(UsbDevice usbDevice) {
        mUsbDeviceConnection = mUsbManager.openDevice(usbDevice);

        Log.d(TAG, "changeToAccessoryMode");
        if (usbDevice == null) {
            return false;
        }
        if (!getProtocolVersion()) {
            Log.e(TAG, "Change Accessory Mode getProtocolVersion Fail");
            return false;
        }
        if (!sendIdentityStrings()) {
            Log.e(TAG, "Change Accessory Mode sendIdentityStrings Fail");
            return false;
        }
        if (!startAccessoryMode()) {
            Log.e(TAG, "Change Accessory Mode startAccessoryMode Fail");
            return false;
        }
        Log.e(TAG, "Change Accessory Mode Success");
        return true;
    }

    private boolean getProtocolVersion() {
        byte[] buffer = new byte[2];
        if (controlTransferIn(AOA_GET_PROTOCOL, 0, 0, buffer) < 0) {
            Log.d(TAG, "get protocol version fail");
            return false;
        }

        int version = buffer[1] << 8 | buffer[0];
        if (version < 1 || version > 2) {
            Log.e(TAG, "usb device not capable of AOA 1.0 or 2.0, version = " + version);
            return false;
        }
        Log.e(TAG, "usb device AOA version is " + version);
        return true;
    }

    private boolean sendIdentityStrings() {
        if (controlTransferOut(AOA_SEND_IDENT, 0, 0, AOA_MANUFACTURER.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_MANUFACTURER fail");
            return false;
        }
        if (controlTransferOut(AOA_SEND_IDENT, 0, 1, AOA_MODEL_NAME.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_MODEL_NAME fail");
            return false;
        }
        if (controlTransferOut(AOA_SEND_IDENT, 0, 2, AOA_DESCRIPTION.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_DESCRIPTION fail");
            return false;
        }
        if (controlTransferOut(AOA_SEND_IDENT, 0, 3, AOA_VERSION.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_VERSION fail");
            return false;
        }
        if (controlTransferOut(AOA_SEND_IDENT, 0, 4, AOA_URI.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_URI fail");
            return false;
        }
        if (controlTransferOut(AOA_SEND_IDENT, 0, 5, AOA_SERIAL_NUMBER.getBytes()) < 0) {
            Log.d(TAG, "send identity AOA_SERIAL_NUMBER fail");
            return false;
        }
        Log.e(TAG, "send indentity string success");
        return true;
    }

    private boolean startAccessoryMode() {
        if (controlTransferOut(AOA_START_ACCESSORY, 0, 0, null) < 0) {
            Log.d(TAG, "start accessory mode fail");
            return false;
        }
        Log.e(TAG, "start accessory mode success");
        return true;
    }

    private int controlTransferOut(int request, int value, int index, byte[] buffer) {
        if (mUsbDeviceConnection == null) {
            return -1;
        }
        return mUsbDeviceConnection.controlTransfer(UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, request,
                value, index, buffer, buffer == null ? 0 : buffer.length, TIME_OUT);
    }

    private int controlTransferIn(int request, int value, int index, byte[] buffer) {
        if (mUsbDeviceConnection == null) {
            return -1;
        }
        return mUsbDeviceConnection.controlTransfer(UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR, request,
                value, index, buffer, buffer == null ? 0 : buffer.length, TIME_OUT);
    }

完成这些步骤后,配件应等待所连接的 USB 设备在配件模式下将其自身重新接入总线,然后重新枚举所连接的设备。该算法通过检查供应商 ID 和产品 ID 来确定设备是否支持配件模式,如果设备成功切换到配件模式,那么供应商 ID 和产品 ID 应该是正确的(例如,与 Google 的供应商 ID 和产品 ID 而不是设备制造商的 ID 相对应)。如果 ID 正确,配件则进而与设备建立通信

 

2.4 枚举设备

当应用程序运行时,如果应用程序有兴趣检查当前连接的所有 USB 设备,它可以枚举总线设备。使用 getDeviceList() 方法获取所有已连接 USB 设备的哈希表,如果要从表中获取设备,通过作为键值传入的 USB 设备名。

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");

如果需要,还可以从哈希表中获取 iterator(迭代器),并逐个处理每个设备:

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
    UsbDevice device = deviceIterator.next();
    //your code
}

2.5 获取与设备进行通信的权限

在与USB设备进行通信之前,应用程序必须获得用户的许可。

如果应用程序使用 intent filter 来发现连接时的 USB 设备,则如果用户允许您的应用程序处理 intent,则它将自动接收权限。如果没有,您必须在连接到设备之前在应用程序中明确请求权限。

在某些情况下,显式请求许可可能是必需的,例如当您的应用程序枚举已连接的USB设备,然后要与其进行通信时。在尝试与之通信之前,您必须检查访问设备的权限。如果没有,用户拒绝访问设备的权限时,您将收到 runtime 错误。

要明确获得许可,首先创建一个广播接收器。该接收器侦听当您调用 requestPermission()时获得广播的意图。对 requestPermission()的调用向用户显示一个对话框,请求连接到设备的权限。以下示例代码展示了如何创建广播接收器:

private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(device != null){
                      //call method to set up device communication
                   }
                }
                else {
                    Log.d(TAG, "permission denied for device " + device);
                }
            }
        }
    }
};

要注册广播接收器,在活动的 onCreate() 方法中添加如下代码:

 

UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

要显示请求用户连接设备权限的对话框,调用 requestPermission()方法

 

UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);

 

2.6 与设备通信

与USB设备的通信可以是同步或异步的。在任一情况下,您应该创建一个新线程来执行所有数据传输,才不会阻塞UI线程。要正确建立与设备的通信,您需要获得要进行通信的设备的相应的UsbInterfaceUsbEndpoint,并使用UsbDeviceConnection在此端点上发送请求。一般来说,您的代码应该:

  1. 检查UsbDevice 对象的属性,如产品 ID,供应商 ID或设备类,以确定是否要与设备进行通信;

  2. 当您确定要与设备通信时,请找到与合适的UsbEndpoint 进行通信的相应UsbInterface。接口可以具有一个或多个端点,并且通常会具有用于双向通信的输入和输出端点;

  3. 找到正确的端点时,在该端点上打开一个UsbDeviceConnection

  4. 使用bulkTransfer()controlTransfer() 方法提供在端点上传输的数据。您应该在另一个线程中执行此步骤,以防止阻塞主 UI 线程。有关在 Android 中使用线程的更多信息,请参阅 Processes and Threads

以下代码片段是进行同步数据传输的简单方法。您的代码应该有更多的逻辑来正确找到正确的接口和端点进行通信,并且还应该在与主UI线程不同的线程中进行数据传输:

int productId = usbDevice.getProductId();
Message msg = new Message();
msg.what = UsbHandler.SHOW_TOAST;
msg.obj = Integer.toHexString(productId);
mUsbHandler.sendMessage(msg);
if (productId == 0x2D00 || productId == 0x2D01) {
    if (mUsbManager.hasPermission(usbDevice)) {
        mUsbDeviceConnection = mUsbManager.openDevice(usbDevice);
        if (mUsbDeviceConnection != null) {
            mUsbInterface = usbDevice.getInterface(0);
            int endpointCount = mUsbInterface.getEndpointCount();
            for (int i = 0; i < endpointCount; i++) {
                UsbEndpoint usbEndpoint = mUsbInterface.getEndpoint(i);
                if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
                        mUsbEndpointOut = usbEndpoint;
                    } else if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
                        mUsbEndpointIn = usbEndpoint;
                    }
                }
            }
        }
    }
}
//接收消息
int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, mBytes, mBytes.length, 3000);
if (i > 0) {
   String text = new String(mBytes, 0, i);
}

//发送消息
mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut, mBytes, mBytes.length, 3000);

 

2.7 终止与设备通信

当你与设备通信完成或者设备拔出时,调用 releaseInterface() 与 close() 方法关闭 UsbInterface 与 UsbDeviceConnection。为了监听拔除事件,如下所示创建广播接收器:

BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

      if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
              unInitDevice();
            }
        }
    }
};

private void unInitDevice() {
    if (null != mAOAConnectThread) {
        mAOAConnectThread.cancel();
        mAOAConnectThread = null;
    }
    if (mUsbDeviceConnection != null) {
        mUsbDeviceConnection.releaseInterface(mUsbInterface);
        mUsbDeviceConnection.close();
        mUsbDeviceConnection = null;
    }
    mUsbEndpointIn = null;
    mUsbEndpointOut = null;
}

3. 参考链接:

https://blog.csdn.net/JAZZSOLDIER/article/details/73741644

https://blog.csdn.net/JAZZSOLDIER/article/details/73776318

https://blog.csdn.net/JAZZSOLDIER/article/details/73849057

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yinhunzw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值