研究android2android aoa通訊時企垦,在網(wǎng)上查詢了很多資料帖渠,這些資料對Accessory模式的描述桑涎,在研究過程中造成了很大的困擾即硼,故此先對基本信息進(jìn)行介紹逃片。
1.1 Host模式與Accessory模式的區(qū)別
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設(shè)備和Android Host設(shè)備兩者枚舉識別工作過程
枚舉過程如圖所示
1. 首先USB Accessory設(shè)備發(fā)起USB控制傳輸進(jìn)行正常的USB設(shè)備枚舉,獲取設(shè)備描述符和配置描述符等信息只酥。
此時大部分Android設(shè)備上報(bào)的還只是普通的HID或MTP設(shè)備
2. 接下來USB Accessory設(shè)備褥实,根據(jù)枚舉的PID/VID呀狼,向?qū)?yīng)的USB設(shè)備,發(fā)起Vendor類型损离,request值為51(0x33)的控制傳輸命令(ACCESSORY_GET_PROTOCOL)哥艇,
看看該Android設(shè)備是否支持USB Accessory功能,如果支持的話會返回所支持的AOA協(xié)議版本(1或2)僻澎。
3. USB Accessory判斷到該Android設(shè)備支持Accessory功能后她奥,
發(fā)起request值為52(0x34)的控制傳輸命令(ACCESSORY_SEND_STRING),
并把該Accessory設(shè)備的相關(guān)信息(包括廠家怎棱,序列號等)告知Android設(shè)備;
4. 最終绷跑,USB Accessory設(shè)備發(fā)起request值為53(0x35)的控制傳輸命令(ACCESSORY_START)拳恋,
通知Android設(shè)備切換到Accessory功能模式開始工作。
至此砸捏,Android Accessory端與Host端的識別工作完成谬运,接下來是通訊:Accessory端使用塊傳輸,Host端使用輸入輸出流垦藏,具體代碼如下梆暖。
1.4 Android USB Accessory設(shè)備和Android設(shè)備兩者枚舉識別工作過程的代碼實(shí)現(xiàn)
Accessory端
1.設(shè)備枚舉
public final String myUsbDevices = "1234/1234";
public static final String[]aoaPidVid = {"2D00/18D1","2D01/18D1","2D02/18D1","2D03/18D1","2D04/18D1","2D05/18D1"};
/**
* 枚舉usb設(shè)備,并修改為Accessory模式</br>
* 本例中只考慮連接一個Accessory設(shè)備的情況
* **/
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設(shè)備是否目標(biāo)設(shè)備
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)){//判斷設(shè)備是否為Accessory設(shè)備
return dev;
}
}
return null;
}
2.初始化Accessory:
查詢Android Host端是否支持AOA協(xié)議掂骏,向Android Host端發(fā)送AndroidAccessory端的廠商/序列號等內(nèi)容轰驳,通知Android Host端切換到Accessory模式
/**
* 配件發(fā)送序號52的USB請求報(bào)文,通過Index字段攜帶配件自身信息弟灼,包括制造商级解、型號、版本田绑、設(shè)備描述勤哗、序 列號URI等。手機(jī)根據(jù)這些信息啟動響應(yīng)的APP
* 配件發(fā)送序號53的USB請求報(bào)文掩驱,切換USB模式芒划,主要是根據(jù)切換的vendorID和productID
* 重新枚舉USB設(shè)備,準(zhǔn)備建立AOA數(shù)據(jù)通道
*/
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協(xié)議
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.使用塊傳輸進(jìn)行通信
Host端
1.枚舉accessory設(shè)備
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)){
//啟動讀取數(shù)據(jù)線程
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進(jìn)行通信
至此Android至Android的Usb aoa通信已完成,以下是完整demo工程