基於LC6500 模塊AOA 通道的建立
1.AOA 介紹
AOA 爲Android Open Accessory的縮寫,簡單點說,就是Android支持的USB 設備形態中的一種。
一個Andorid系統的AOA模式分爲兩種:
- HOST模式:
Android 設備在HOST模式下,將會提供電源給外部設備並與之建立通信。
- Accessory模式:
Android 設備在Accessory模式下,將與一個可識別Android USB 設備的設備進行連接並進行通信,且將由此設備給予Accessory 模式下的Andorid設備以電源。
2.具體模式解析
\* MERGEFORMAT
2.1 HOST 模式
處於HOST 模式下的Android device,通過檢測連接上的USB 設備是否處於Accessory模式,如果處於accessory模式,則直接建立通道;如果不處於Accessory模式,則通過control transfer 要求連接的USB 設備先切換爲accessory模式再建立通道。
我們具體分析一下CLIENT端連接HOST端時未處於Accessory模式下時的流程
注: 因爲涉及到監聽USB device 的狀態,我們一下代碼都依賴於libusb的庫。
2.1.1 監聽USB設備的連接/拔出
int main(int argc, char* argv[]) {
struct usb_host_context* context = usb_host_init();
... ...
// this will never return so it is safe to pass thiz directly
usb_host_run(context, usb_device_added, usb_device_removed, NULL, NULL);
return 0;
}
main()的主要作用就是:
- 獲取libusbhost 庫環境;
- 調用usb_host_run函數註冊監聽USB device設備的添加或拔出。USB devices添加時,會調用usb_device_added的回調;USB devices拔出時,會調用usb_device_removed的回調。
2.1.2 判斷CLIENT端是處於accessory模式
static int usb_device_added(const char *devname, void* client_data) {
... ...
struct usb_device *device = usb_device_open(devname)
... ...
vendorId = usb_device_get_vendor_id(device);
productId = usb_device_get_product_id(device);
if (vendorId == 0x18D1 || vendorId == 0x22B8 || vendorId == 0x04e8) {
//CLIENT端支持accessory模式
if (!sDevice && (productId == 0x2D00 || productId == 0x2D01)){
//CLIENT端處於accessory模式
} else {
//CLIENT端不處於accessory模式
}
}
... ...
}
usb_device_added函數的主要作用是:
- 獲取連接設備的Vid 和Pid 數值;
- 先判斷Vid是否爲0x18D1 或者0x11B8 或者 0x04e8,如果爲以上Vid 中的一種,表示此設備支持accessory模式;如果不爲上述Vid中的一種,則表示不支持accessory模式;
- 判斷Pid是否爲0x2D00 或0x2D01中的一種,如果是,則表示設備當前處於accessory模式;如果不是,則表示設備當前不處於accessory模式;
2.1.3 USB device不爲accessory設備時
2.1.3.1 HOST端發送control transfer,要求client端切換accessory模式
代碼如下:
... ...
uint16_t protocol;
ret = usb_device_control_transfer(device, USB_DIR_IN | USB_TYPE_VENDOR,
ACCESSORY_GET_PROTOCOL, 0, 0, &protocol, sizeof(protocol), 0);
通過調用usb_device_control_transfer,發送一個51 control 請求(“Get Protocol”)來區分是否設備支持 Android accessory 協議。 一個非0數值會被返回,如果對於這個設備來說 accessory 協議是支持的。這個請求是一個控制的請求,在 endpoint 0 上 有如下特徵:
requestType : USB_DIR_IN | USB_TYPE_VENDOR
request : 51
value : 0
index : 0
data : protocol version num ()
2.1.3.2 HOST端發送control transfer,要求client端啓動特定的application
代碼如下:
... ...
send_string(device, ACCESSORY_STRING_MANUFACTURER, "Google, Inc.");
send_string(device, ACCESSORY_STRING_MODEL, "AccessoryChat");
send_string(device, ACCESSORY_STRING_DESCRIPTION, "Accessory Chat");
send_string(device, ACCESSORY_STRING_VERSION, "1.0");
send_string(device, ACCESSORY_STRING_URI, "http://www.android.com");
send_string(device, ACCESSORY_STRING_SERIAL, "1234567890");
ret=usb_device_control_transfer(device,USB_DIR_OUT| USB_TYPE_VENDOR,ACCESSORY_START, 0, 0, 0, 0, 0);
Client端會解析此消息,並在client端篩選出符合的application,具體的步驟在Accessory模式下講解。
2.1.4 USB device 爲accessory模式
讀數據:
while (sDevice && ret >= 0) {
char buffer[16384];
ret = usb_device_bulk_transfer(sDevice, endpoint, buffer, sizeof(buffer), 1000);
if (ret < 0 && errno == ETIMEDOUT)
ret = 0;
if (ret > 0) {
fwrite(buffer, 1, ret, stdout);
printf("\n");
fflush(stdout);
}
}
寫數據:
while (ret >= 0) {
char buffer[16384];
char *line = fgets(buffer, sizeof(buffer), stdin);
if (!line || !sDevice)
break;
ret = usb_device_bulk_transfer(sDevice, endpoint, line, strlen(line), 1000);
}
2.2 Accessory模式
2.2.1 切換accessory模式
2.2.1.1 kernel上報Uevent
Kernel層解析有HOST段發送過來的control protocol,並上報開啓accessory的Uevent
具體流程:
略... ...
2.2.1.2 Framework層切換accessory模式
1. UsbDeviceManager 監聽kernel上報的Uevent,“ACCESSORY”關鍵字爲“START”時,開時切換accessory模式;
/frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java
... ...
private final UEventObserver mUEventObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
String state = event.get("USB_STATE");
String accessory = event.get("ACCESSORY");
if (state != null) {
mHandler.updateState(state);
} else if ("START".equals(accessory)) {
if (DEBUG) Slog.d(TAG, "got accessory start");
startAccessoryMode();
}
}
};
- startAccessoryMode()
private void startAccessoryMode() {
... ...
// 判斷當前系統是否支持accessory模式
... ...
// 確定需要下發的USB系統屬性
String functions = null;
if (enableAccessory && enableAudio) {
functions = UsbManager.USB_FUNCTION_ACCESSORY + ","
+ UsbManager.USB_FUNCTION_AUDIO_SOURCE;
} else if (enableAccessory) {
functions = UsbManager.USB_FUNCTION_ACCESSORY;
} else if (enableAudio) {
functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE;
}
// 調用setCurrentFunctions下發的USB系統屬性
if (functions != null) {
mAccessoryModeRequestTime = SystemClock.elapsedRealtime();
setCurrentFunctions(functions);
}
}
startAccessoryMode函數的主要作用在於:
1.調用nativeGetAccessoryStrings()
/frameworks/base/services/core/jni/com_android_server_UsbDeviceManager.cpp
static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env,jobject /* thiz */)
{
... ...
set_accessory_string(env, fd,ACCESSORY_GET_STRING_MANUFACTURER, strArray, 0);
set_accessory_string(env, fd, ACCESSORY_GET_STRING_MODEL, strArray, 1);
set_accessory_string(env, fd,ACCESSORY_GET_STRING_DESCRIPTION, strArray, 2);
set_accessory_string(env, fd, ACCESSORY_GET_STRING_VERSION, strArray, 3);
set_accessory_string(env, fd, ACCESSORY_GET_STRING_URI, strArray, 4);
set_accessory_string(env, fd, ACCESSORY_GET_STRING_SERIAL, strArray, 5);
... ...
}
解析之前在2.1.3.2 中提及的有HOST端發送的特定application應用的消息,其中:
通過MANUFACTURER_STRING 和MODEL_STRING 兩個標誌位來判斷accessory是否enable。
- 調用nativeGetAudioMode()
/frameworks/base/services/core/jni/com_android_server_UsbDeviceManager.cpp
static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
... ...
int result = ioctl(fd, ACCESSORY_GET_AUDIO_MODE);
close(fd);
return result;
}
其中: #define DRIVER_NAME "/dev/usb_accessory"
nativeGetAudioMode函數的作用就是獲取"/dev/usb_accessory"節點中的audio文件流,如果此節點下有視頻流,這說明HOST端正在向client傳送audio文件流,因此USB 的設備狀態中應該包含“audio_source”屬性,已確保USB 驅動中對audio流的傳送的使能。
2.2.2 應用層進行通信
2.2.2.1 應用被喚起
1.在應用的res/xml/中添加accessory_filter.xml文件:
<resources>
<usb-accessory manufacturer="Google, Inc." model="AccessoryChat" type="Sample Program" version="1.0" />
</resources>
只要在accessory_filter.xml中manufacturer,model等關鍵字和在2.1.3.1 中由HOST端發送的篩選信息相同則被喚起。
2.需要在AndroidManifest.xml中註明:
<uses-feature android:name="android.hardware.usb.accessory" />
... ...
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
2.2.2.2 應用監聽USB中accessory模式的開啓
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (ACTION_USB_PERMISSION.equals(intent.getAction())) {
... ...
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (accessory != null) {
openAccessory(accessory);
}
}
應用監聽到USB 屬性中包含“accessory”屬性,則表示應用所運行的CLIENT端已和HOST端開啓AOA通道。
2.2.2.3 應用進行AOA通道下的數據傳輸
應用調用最終會調用UsbManager.openAccessory()獲取accessory的FileDescriptor。
private void openAccessory(UsbAccessory accessory) {
... ...
mFileDescriptor = mUsbManager.openAccessory(accessory);
... ...
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
... ...
}
UsbManager.openAccessory()最終會調用com_android_server_UsbDeviceManager:android_server_UsbDeviceManager_openAccessory:
static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
... ...
jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
... ...
return env->NewObject(gParcelFileDescriptorOffsets.mClass,
gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}
其中 #define DRIVER_NAME "/dev/usb_accessory"
由此可以看到,應用在AOA 通道下的文件讀寫即是對 "/dev/usb_accessory" 這個節點下fd的讀/寫。