Google对于Android Accessory的应用端开发有详细的例子,我这里就不发代码。实在看不懂官方例子,再找我吧,我把代码贴上来。主要提几个要点:
1、activity配置的intent-filter,是在accessory通过AOA协议连接设备后,系统决定使用哪个应用的配置。例如我们配置的manufacturer、model、version,只有accessory连接时提供的信息一致,那么就可以提示是否打开应用。当然,我们在应用里面也可以通过receiver接收绑定和解绑的事件。官方例子里面的接收器主要是作为申请权限用的,但其实都一样。
2、应用里面要获取Accessory对象,主要是通过查询绑定列表来获取的。当然在接收器里面也可以获得。不过需要自己给接收器增加接收action。
UsbAccessory[] list = mUsbManager.getAccessoryList();
上面就是获取当前绑定的accessory。若这个返回为空,说明绑定还没有成功。
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
registerReceiver(mUsbReceiver, filter);
官方例子里面,这个接收器只定义了获取权限的action,其实自己可以增加绑定和解绑的事件。
3、我在测试的时候,若accessory没有解绑(拔掉usb线),直接关闭fd是无法结束那个读取进程的!!即使进程interrupt也是没有任何作用。总之,就是被一直阻塞。我看了fd的close代码,确实有调用释放,但是进程无法释放。初步推测是jni那边的超时循环有问题。这个问题在usb4java里面也有,不过可以abort消息队列解决问题。具体细节就不说了。之所以谈这个,是因为若你在这里丢失fd,就无法再通过open打开连接,它会一直返回null。原因就是那个读取进程没有结束。处理方法有两种,一种真的拔线来结束,另一种,accessory随便发送一个短数据,我们读取后就不要再read,退出进程。由于大家基本没有这个操作,所以也不用太计较就是,只是我偶然手贱发现的问题。
mFileDescriptor = mUsbManager.openAccessory(mAccessory);
读取进程没结束,通过上面代码再获取fd会一直为空!
4、手机连接电脑后,默认都是存储设备。有开启adb,那么就多了个adb。由于这两个设备类别经常使用,所以我就没有修改它们的驱动。而是把手机的usb模式切换为Audio Source,其实就是官方说到的AOA Audio设备类别。这个类别的VID和PID跟官方一样。出现这个后,安装libusbK驱动就可以操作了。先提一点,若你同时开启adb,那么设备类别就是AOA Audio+ADB。总之,切换为一个你不常用的设备类型,然后修改它的驱动,为我们后面发送AOA协议使用。对于没有使用过libusb或者操作过驱动的用户,可能对VID和PID不理解。简单讲,VID是vendor id,PID是product id。一般通过这两个来标记一个usb设备,当然还有接口interface和端点endpoint。由于是usb的概念,这里也说不清除,只能自己理解。
USB\VID_18D1&PID_2D02&MI_00 AOA的Audio
USB\VID_18D1&PID_2D00&MI_00 AOA的Accessory
USB\VID_18D1&PID_2D04&MI_00 AOA的Accessory+Audio
上面是我目前用到的设备类别。当然,到时操作到那步,检测到设备后,再依次替换驱动就行,没有必要一开始就全部注册驱动。对于有些人想去找驱动的,这是没必要的。你只要给它们安装你能够操作的库的驱动就行。由于我使用libusb来操作usb设备,所以我给它们安装的就是libusbK驱动。
以上准备好后,就可以开发电脑端,即accessory。由于github上有linux-adk这个例子,而且代码也挺规范,所以我这里就不细讲代码内容。主要提几个要点:
1、电脑端若没有通过AOA协议连接手机,手机那边是无法进入绑定状态的。换句话说,手机无法自动进入accessory的设备模式。我们通过AOA协议连接手机,其实就是告诉手机我们的信息,手机找到对应应用(注册了上面说到的intent-filter)后,才有连接的价值。linux-adk的代码,在检测到预定义的设备类别后,直接进入通信模式,其实是有经过AOA协议连接后的操作。提这个,主要是我是通过AOA Audio这个设备类别来发送AOA协议的。这时有些人就认为,既然设备已经进入AOA Audio模式,为何不直接通信。因为没有经过AOA协议连接的设备,手机端是无法知道绑定信息的,即使你的设备类别已经是accessory的预定义类别。其实这个跟预定义设备类别没有太大关系,主要还是发送协议信息,激活手机的绑定状态。那些预定义设备类别,只要是表明手机当前处于哪种工作状态(协议)而已。
2、虽说linux-adk是linux平台环境使用的,其实只要使用libusb开发,原理都是一样,甚至调用的接口都是一样的。所以,习惯看linux-adk的,可以先看它的代码。注释内容不理解,再来看一下我的代码。我主要增加了一些注意事项,其它大同小异。
linux-adk的代码地址:
https://github.com/gibsson/linux-adk
另外,我的代码里面包含对usb一些操作接口,这些其实普通的检查和使用逻辑。觉得无法理解,再找我要那些代码。
这是accessory的信息类。为了连接手机后,能够找到对应的应用,保持这些信息跟intent-filter一致就行:
package org.ufo.aoa;
public class Accessory {
// AOA Audio
public String device = "VID_18D1&PID_2D02";
// AOA Audio+ADB
// public String device = "VID_18D1&PID_2D03";
// ADB
// public String device = "VID_0E8D&PID_201D&MI_01";
public String manufacturer = "UFO";
public String model = "TestKit";
public String description = "Usb Test";
public String version = "1.0";
public String url = "https://www.iot2ai.top";
public String serial = "0000000012345678";
}
这是发送AOA协议信息,建立连接的过程,即激活手机绑定的流程。只有正确走完这个流程,手机才能绑定成功,否则是无法使用的。我这里绑定成功后,没有执行其它读写操作。这个我是另外开发的。因为激活成功后,手机的设备类别就固定为这个(除非拔掉usb线)。之后你要怎样操作都可以。这些就跟普通usb设备通信一样。其实,使用accessory的唯一要点,就是如何激活手机绑定逻辑。手机正确切换为accessory通信方式后,就可以使用普通的usb操作进行通信。
代码里面加的注释就是需要注意的信息。其它不理解的代码再单独找我吧,我再补充上来。
package org.ufo.aoa;
import javax.swing.JOptionPane;
import javax.usb.UsbConst;
import javax.usb.UsbControlIrp;
import javax.usb.UsbDevice;
import javax.usb.util.UsbUtil;
import org.ufo.usb.InfoUsb;
import org.ufo.usb.ReadUsb;
public class CtrlAoa {
/* Android Open Accessory protocol defines */
public static final int AOA_GET_PROTOCOL = 51;
public static final int AOA_SEND_IDENT = 52;
public static final int AOA_START_ACCESSORY = 53;
public static final int AOA_REGISTER_HID = 54;
public static final int AOA_UNREGISTER_HID = 55;
public static final int AOA_SET_HID_REPORT_DESC = 56;
public static final int AOA_SEND_HID_EVENT = 57;
public static final int AOA_AUDIO_SUPPORT = 58;
/* String IDs */
public static final int AOA_STRING_MAN_ID = 0;
public static final int AOA_STRING_MOD_ID = 1;
public static final int AOA_STRING_DSC_ID = 2;
public static final int AOA_STRING_VER_ID = 3;
public static final int AOA_STRING_URL_ID = 4;
public static final int AOA_STRING_SER_ID = 5;
/* Product IDs / Vendor IDs */
public static final int AOA_ACCESSORY_VID = 0x18D1; /* Google */
public static final int AOA_ACCESSORY_PID = 0x2D00; /* accessory */
public static final int AOA_ACCESSORY_ADB_PID = 0x2D01; /* accessory + adb */
public static final int AOA_AUDIO_PID = 0x2D02; /* audio */
public static final int AOA_AUDIO_ADB_PID = 0x2D03; /* audio + adb */
public static final int AOA_ACCESSORY_AUDIO_PID = 0x2D04; /* accessory + audio */
public static final int AOA_ACCESSORY_AUDIO_ADB_PID = 0x2D05; /* accessory + audio + adb */
/* Endpoint Addresses get from interface descriptor */
public static final int AOA_ACCESSORY_EP_IN = 0x81;
public static final int AOA_ACCESSORY_EP_OUT = 0x02;
public static final int AOA_ACCESSORY_INTERFACE = 0x00;
public static void listAoa() throws Exception {
// 枚举全部可能的设备
// 激活成功后,默认会使用AOA_ACCESSORY_PID设备
// 若安卓手机启用adb,则连接设备包含adb的模式
// 若激活时发送audio支持指令,则连接设备包含audio的模式
System.out.println("------ ACCESSORY ------");
String id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_PID);
InfoUsb.listInterface(id, null);
System.out.println("------ ACCESSORY_ADB ------");
id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_ADB_PID);
InfoUsb.listInterface(id, null);
System.out.println("------ AUDIO ------");
id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_AUDIO_PID);
InfoUsb.listInterface(id, null);
System.out.println("------ AUDIO_ADB ------");
id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_AUDIO_ADB_PID);
InfoUsb.listInterface(id, null);
System.out.println("------ ACCESSORY_AUDIO ------");
id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_AUDIO_PID);
InfoUsb.listInterface(id, null);
System.out.println("------ ACCESSORY_AUDIO_ADB ------");
id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_AUDIO_ADB_PID);
InfoUsb.listInterface(id, null);
}
public static void main(String[] args) throws Exception {
Accessory accessory = new Accessory();
final String inputSrc = JOptionPane.showInputDialog("请输入设备ID", accessory.device);
if (inputSrc == null || inputSrc.length() == 0) return;
accessory.device = inputSrc;
// 枚举全部设备
listAoa();
// 检查指定设备
System.out.println("------------");
String id = ReadUsb.getId(accessory.device);
String mi = ReadUsb.getMi(accessory.device);
UsbDevice usbDevice = InfoUsb.listInterface(id, mi);
if (usbDevice == null) return;
// 发送控制信息激活附件功能(安卓手机连接usb后,默认都不是附件模式)
// 由于安卓手机需要激活后才能获得绑定的附件,所以需要执行以下协议指令
// 应用只要注册了同样的附件信息,当附件激活成功后,会提示是否打开应用
// 激活后,安卓手机会切换为指定的设备信息,安装libusbK驱动后,就可以跟应用进行通信
// 发送和接收测试:
// 单次发送数据大小每次只能小于等于8个字节(但是在节点的配置信息里面,最大包大小是512),否则即使发送成功,应用也无法收到
// 读取数据时,若配置超时,超时后,一定要中断本次任务,否则会一直无法读取数据。问题描述参考ReadUsb的说明
System.out.println("------------");
try {
// 获取版本信息指令一定要正确执行,否则下一步指令无法执行
/* Now asking if device supports Android Open Accessory protocol */
byte bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_IN | UsbConst.REQUESTTYPE_TYPE_VENDOR;
byte bRequest = AOA_GET_PROTOCOL;
short wValue = 0;
short wIndex = 0;
UsbControlIrp usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
// 需要指定读取缓存大小和目标
usbControlIrp.setData(new byte[2]);
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
// 这个跟我们刚才指定的缓存是一样的,usb4java读取后会拷贝到这个缓存
byte[] ctrlData = usbControlIrp.getData();
short aoa_version = UsbUtil.toShort(ctrlData[1], ctrlData[0]);
System.out.printf("Device supports AOA %d.0!%n", aoa_version);
/* Some Android devices require a waiting period between transfer calls */
Thread.sleep(10000);
/* In case of a no_app accessory, the version must be >= 2 */
if (aoa_version < 2 && accessory.manufacturer == null) {
System.out.printf("Connecting without an Android App only for AOA 2.0%n");
return;
}
System.out.printf("Sending identification to the device%n");
if (accessory.manufacturer != null) {
System.out.printf(" sending manufacturer: %s%n", accessory.manufacturer);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_MAN_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.manufacturer.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (accessory.model != null) {
System.out.printf(" sending model: %s%n", accessory.model);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_MOD_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.model.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (accessory.description != null) {
System.out.printf(" sending description: %s%n", accessory.description);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_DSC_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.description.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (accessory.version != null) {
System.out.printf(" sending version: %s%n", accessory.version);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_VER_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.version.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (accessory.url != null) {
System.out.printf(" sending url: %s%n", accessory.url);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_URL_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.url.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (accessory.serial != null) {
System.out.printf(" sending serial: %s%n", accessory.serial);
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_SEND_IDENT;
wValue = 0;
wIndex = AOA_STRING_SER_ID;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbControlIrp.setData(accessory.serial.getBytes());
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
if (aoa_version >= 2) {
// 增加这条指令,可以让设备类型包含audio模式
System.out.printf(" asking for audio support%n");
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_AUDIO_SUPPORT;
wValue = 1;
wIndex = 0;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
}
System.out.printf("Turning the device in Accessory mode%n");
bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
bRequest = AOA_START_ACCESSORY;
wValue = 0;
wIndex = 0;
usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
usbDevice.asyncSubmit(usbControlIrp);
usbControlIrp.waitUntilComplete();
if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
Thread.sleep(10000);
// 枚举全部设备
listAoa();
} catch (Exception e) {
e.printStackTrace();
}
}
}