diff options
Diffstat (limited to 'tests/UsbHostExternalManagmentTest')
18 files changed, 1503 insertions, 0 deletions
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk b/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk new file mode 100644 index 000000000000..3137a7338084 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk @@ -0,0 +1,35 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH:= $(call my-dir) + +################################################## + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := AoapTestDeviceApp + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.xml new file mode 100644 index 000000000000..99bb520ee902 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.hardware.usb.aoapdevicetest" > + <application android:label="UsbAoapDeviceTestApp" > + <activity android:name=".UsbAoapDeviceTestActivity" + android:configChanges="keyboard|keyboardHidden" > + <intent-filter> + <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> + </application> +</manifest> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml new file mode 100644 index 000000000000..cc71cf9fc37a --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > +</LinearLayout> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.xml new file mode 100644 index 000000000000..d854a45923be --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <usb-accessory model="AOAP Test App" manufacturer="Android"/> +</resources> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java b/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java new file mode 100644 index 000000000000..aa4f8caa0b20 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.aoapdevicetest; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import libcore.io.IoUtils; + +public class UsbAoapDeviceTestActivity extends Activity { + private static final String TAG = UsbAoapDeviceTestActivity.class.getSimpleName(); + private static final boolean DBG = true; + + private static final String ACTION_USB_ACCESSORY_PERMISSION = + "com.android.hardware.usb.aoapdevicetest.ACTION_USB_ACCESSORY_PERMISSION"; + + private UsbManager mUsbManager; + private AccessoryReceiver mReceiver; + private ParcelFileDescriptor mFd; + private ReaderThread mReaderThread; + private UsbAccessory mAccessory; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.device); + + mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); + filter.addAction(ACTION_USB_ACCESSORY_PERMISSION); + mReceiver = new AccessoryReceiver(); + registerReceiver(mReceiver, filter); + + Intent intent = getIntent(); + if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { + UsbAccessory accessory = + (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (accessory != null) { + onAccessoryAttached(accessory); + } else { + throw new RuntimeException("USB accessory is null."); + } + } else { + finish(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + IoUtils.closeQuietly(mFd); + if (mReaderThread != null) { + mReaderThread.requestToQuit(); + try { + mReaderThread.join(1000); + } catch (InterruptedException e) { + } + if (mReaderThread.isAlive()) { // reader thread stuck + Log.w(TAG, "ReaderThread still alive"); + } + } + } + + private void onAccessoryAttached(UsbAccessory accessory) { + Log.i(TAG, "Starting AOAP discovery protocol, accessory attached: " + accessory); + // Check whether we have permission to access the accessory. + if (!mUsbManager.hasPermission(accessory)) { + Log.i(TAG, "Prompting the user for access to the accessory."); + Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION); + intent.setPackage(getPackageName()); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + this, 0, intent, PendingIntent.FLAG_ONE_SHOT); + mUsbManager.requestPermission(accessory, pendingIntent); + return; + } + mFd = mUsbManager.openAccessory(accessory); + if (mFd == null) { + Log.e(TAG, "UsbManager.openAccessory returned null"); + finish(); + return; + } + mAccessory = accessory; + mReaderThread = new ReaderThread(mFd); + mReaderThread.start(); + } + + private void onAccessoryDetached(UsbAccessory accessory) { + Log.i(TAG, "Accessory detached: " + accessory); + finish(); + } + + private class ReaderThread extends Thread { + private boolean mShouldQuit = false; + private final FileInputStream mInputStream; + private final FileOutputStream mOutputStream; + private final byte[] mBuffer = new byte[16384]; + + private ReaderThread(ParcelFileDescriptor fd) { + super("AOAP"); + mInputStream = new FileInputStream(fd.getFileDescriptor()); + mOutputStream = new FileOutputStream(fd.getFileDescriptor()); + } + + private synchronized void requestToQuit() { + mShouldQuit = true; + } + + private synchronized boolean shouldQuit() { + return mShouldQuit; + } + + @Override + public void run() { + while (!shouldQuit()) { + try { + int read = mInputStream.read(mBuffer); + } catch (IOException e) { + Log.i(TAG, "ReaderThread IOException", e); + // AOAP App should release FD when IOException happens. + // If FD is kept, device will not behave nicely on reset and multiple reset + // can be required. + finish(); + return; + } + } + } + } + + private class AccessoryReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (accessory != null) { + String action = intent.getAction(); + if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { + onAccessoryAttached(accessory); + } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) { + if (mAccessory != null && mAccessory.equals(accessory)) { + onAccessoryDetached(accessory); + } + } else if (action.equals(ACTION_USB_ACCESSORY_PERMISSION)) { + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + Log.i(TAG, "Accessory permission granted: " + accessory); + onAccessoryAttached(accessory); + } else { + Log.e(TAG, "Accessory permission denied: " + accessory); + finish(); + } + } + } + } + } +} diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk b/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk new file mode 100644 index 000000000000..354e8c9b7bf4 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk @@ -0,0 +1,35 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH:= $(call my-dir) + +################################################## + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := AoapTestHostApp + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.xml new file mode 100644 index 000000000000..8cc470eaa85c --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.hardware.usb.aoaphosttest" > + <application android:label="UsbAoapHostTestApp" > + <activity android:name=".UsbAoapHostTestActivity" + android:configChanges="keyboard|keyboardHidden" > + <intent-filter> + <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> + </intent-filter> + <meta-data + android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" + android:resource="@xml/usb_device_filter"/> + </activity> + </application> +</manifest> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml new file mode 100644 index 000000000000..cc71cf9fc37a --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > +</LinearLayout> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml new file mode 100644 index 000000000000..0509e8976164 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <!-- Android USB accessory: accessory --> + <usb-device vendor-id="16601" product-id="11520" /> + <!-- Android USB accessory: accessory + adb --> + <usb-device vendor-id="16601" product-id="11521" /> + <!-- not suppoted by UsbService, but external host management can use this. --> + <usb-aoap-device model="AOAP Test App" manufacturer="Android" version="1.0" /> +</resources> diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java b/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java new file mode 100644 index 000000000000..6e2dc5d65fa7 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.aoaphosttest; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.text.TextUtils; +import android.util.Log; + +import libcore.io.IoUtils; + +public class UsbAoapHostTestActivity extends Activity { + + private static final String TAG = UsbAoapHostTestActivity.class.getSimpleName(); + + private UsbManager mUsbManager; + private UsbStateReceiver mReceiver; + private UsbDevice mUsbDevice; + private UsbDeviceConnection mUsbConnection; + private ReaderThread mReaderThread; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.host); + + mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + mReceiver = new UsbStateReceiver(); + registerReceiver(mReceiver, filter); + + Intent intent = getIntent(); + if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + mUsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + mUsbConnection = mUsbManager.openDevice(mUsbDevice); + mReaderThread = new ReaderThread(mUsbDevice, mUsbConnection); + mReaderThread.start(); + } else { + finish(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + if (mUsbConnection != null) { + mUsbConnection.close(); + } + if (mReaderThread != null) { + mReaderThread.requestToQuit(); + try { + mReaderThread.join(1000); + } catch (InterruptedException e) { + } + if (mReaderThread.isAlive()) { // reader thread stuck + throw new RuntimeException("ReaderThread still alive"); + } + } + } + + private static boolean isDevicesMatching(UsbDevice l, UsbDevice r) { + if (l.getVendorId() == r.getVendorId() && l.getProductId() == r.getProductId() && + TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) { + return true; + } + return false; + } + + private class ReaderThread extends Thread { + private boolean mShouldQuit = false; + private final UsbDevice mDevice; + private final UsbDeviceConnection mUsbConnection; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final byte[] mBuffer = new byte[16384]; + + private ReaderThread(UsbDevice device, UsbDeviceConnection conn) { + super("AOAP"); + mDevice = device; + mUsbConnection = conn; + UsbInterface iface = mDevice.getInterface(0); + // Setup bulk endpoints. + UsbEndpoint bulkIn = null; + UsbEndpoint bulkOut = null; + for (int i = 0; i < iface.getEndpointCount(); i++) { + UsbEndpoint ep = iface.getEndpoint(i); + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + if (bulkIn == null) { + bulkIn = ep; + } + } else { + if (bulkOut == null) { + bulkOut = ep; + } + } + } + if (bulkIn == null || bulkOut == null) { + throw new IllegalStateException("Unable to find bulk endpoints"); + } + mBulkIn = bulkIn; + mBulkOut = bulkOut; + } + + private synchronized void requestToQuit() { + mShouldQuit = true; + } + + private synchronized boolean shouldQuit() { + return mShouldQuit; + } + + @Override + public void run() { + while (!shouldQuit()) { + int read = mUsbConnection.bulkTransfer(mBulkIn, mBuffer, mBuffer.length, + Integer.MAX_VALUE); + if (read < 0) { + throw new RuntimeException("bulkTransfer failed, read = " + read); + } + } + } + } + + private class UsbStateReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { + UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (isDevicesMatching(mUsbDevice, device)) { + finish(); + } + } + } + } +} diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk new file mode 100644 index 000000000000..2d6d6ea8cdcc --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk @@ -0,0 +1,37 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH:= $(call my-dir) + +################################################## + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := UsbHostExternalManagementTestApp + +LOCAL_PRIVILEGED_MODULE := true +# TODO remove tests tag +#LOCAL_MODULE_TAGS := tests +#LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..97bbefb5c4af --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.hardware.usb.externalmanagementtest" > + + <uses-permission android:name="android.permission.MANAGE_USB" /> + <application android:label="UsbHostExternalManagementTestApp" > + <activity android:name=".UsbHostManagementActivity" + android:configChanges="keyboard|keyboardHidden" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + <intent-filter> + <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml new file mode 100644 index 000000000000..51911846d78c --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + <TextView android:id="@+id/device_info_text" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/current_device" + /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal" > + <Button android:id="@+id/start_aoap_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/start_aoap" + /> + <Button android:id="@+id/start_aoap_activity_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/start_aoap_activity" + /> + <Button android:id="@+id/reset_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/usb_reset" + /> + <Button android:id="@+id/finish_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/finish_app" + /> + </LinearLayout> + <TextView android:id="@+id/aoap_apps_text" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/aoap_app_msg" + /> + +</LinearLayout> diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml new file mode 100644 index 000000000000..79d2c4376662 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="app_title">UsbHostExternalManagementTestApp</string> + <string name="current_device">No device</string> + <string name="aoap_app_msg">AOAP App message</string> + <string name="usb_reset">Reset USB</string> + <string name="start_aoap">Start Test AOAP</string> + <string name="start_aoap_activity">Start Test AOAP Activity</string> + <string name="finish_app">Finish</string> +</resources> diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java new file mode 100644 index 000000000000..89dc441f60bf --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.externalmanagementtest; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +public class AoapInterface { + /** + * Use Google Vendor ID when in accessory mode + */ + public static final int USB_ACCESSORY_VENDOR_ID = 0x18D1; + + /** + * Product ID to use when in accessory mode + */ + public static final int USB_ACCESSORY_PRODUCT_ID = 0x2D00; + + /** + * Product ID to use when in accessory mode and adb is enabled + */ + public static final int USB_ACCESSORY_ADB_PRODUCT_ID = 0x2D01; + + /** + * Indexes for strings sent by the host via ACCESSORY_SEND_STRING + */ + public static final int ACCESSORY_STRING_MANUFACTURER = 0; + public static final int ACCESSORY_STRING_MODEL = 1; + public static final int ACCESSORY_STRING_DESCRIPTION = 2; + public static final int ACCESSORY_STRING_VERSION = 3; + public static final int ACCESSORY_STRING_URI = 4; + public static final int ACCESSORY_STRING_SERIAL = 5; + + /** + * Control request for retrieving device's protocol version + * + * requestType: USB_DIR_IN | USB_TYPE_VENDOR + * request: ACCESSORY_GET_PROTOCOL + * value: 0 + * index: 0 + * data version number (16 bits little endian) + * 1 for original accessory support + * 2 adds HID and device to host audio support + */ + public static final int ACCESSORY_GET_PROTOCOL = 51; + + /** + * Control request for host to send a string to the device + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SEND_STRING + * value: 0 + * index: string ID + * data zero terminated UTF8 string + * + * The device can later retrieve these strings via the + * ACCESSORY_GET_STRING_* ioctls + */ + public static final int ACCESSORY_SEND_STRING = 52; + + /** + * Control request for starting device in accessory mode. + * The host sends this after setting all its strings to the device. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_START + * value: 0 + * index: 0 + * data none + */ + public static final int ACCESSORY_START = 53; + + /** + * Max payload size for AOAP. Limited by driver. + */ + public static final int MAX_PAYLOAD_SIZE = 16384; + + private static final String TAG = AoapInterface.class.getSimpleName(); + + public static int getProtocol(UsbDeviceConnection conn) { + byte buffer[] = new byte[2]; + int len = conn.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR, + AoapInterface.ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, 10000); + if (len != 2) { + return -1; + } + return (buffer[1] << 8) | buffer[0]; + } + + public static void sendString(UsbDeviceConnection conn, int index, String string) { + byte[] buffer = (string + "\0").getBytes(); + int len = conn.controlTransfer( + UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, + AoapInterface.ACCESSORY_SEND_STRING, 0, index, + buffer, buffer.length, 10000); + if (len != buffer.length) { + throw new RuntimeException("Failed to send string " + index + ": \"" + string + "\""); + } else { + Log.i(TAG, "Sent string " + index + ": \"" + string + "\""); + } + } + + public static void sendAoapStart(UsbDeviceConnection conn) { + int len = conn.controlTransfer( + UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR, + AoapInterface.ACCESSORY_START, 0, 0, null, 0, 10000); + if (len < 0) { + throw new RuntimeException("control transfer for accessory start failed:" + len); + } + } + + public static boolean isDeviceInAoapMode(UsbDevice device) { + final int vid = device.getVendorId(); + final int pid = device.getProductId(); + return vid == USB_ACCESSORY_VENDOR_ID + && (pid == USB_ACCESSORY_PRODUCT_ID + || pid == USB_ACCESSORY_ADB_PRODUCT_ID); + } +} diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java new file mode 100644 index 000000000000..1cb394e4d2ab --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.externalmanagementtest; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.util.LinkedList; + +import dalvik.system.CloseGuard; + +public class UsbDeviceStateController { + + public interface UsbDeviceStateListener { + void onDeviceResetComplete(UsbDevice device); + void onAoapStartComplete(UsbDevice devie); + } + + private static final String TAG = UsbDeviceStateController.class.getSimpleName(); + + private static final int MAX_USB_STATE_CHANGE_WAIT = 5; + private static final long USB_STATE_CHANGE_WAIT_TIMEOUT_MS = 500; + + private final Context mContext; + private final UsbDeviceStateListener mListener; + private final UsbManager mUsbManager; + private final HandlerThread mHandlerThread; + private final UsbStateHandler mHandler; + private final UsbDeviceBroadcastReceiver mUsbStateBroadcastReceiver; + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private final Object mUsbConnectionChangeWait = new Object(); + private final LinkedList<UsbDevice> mDevicesRemoved = new LinkedList<>(); + private final LinkedList<UsbDevice> mDevicesAdded = new LinkedList<>(); + private boolean mShouldQuit = false; + + public UsbDeviceStateController(Context context, UsbDeviceStateListener listener, + UsbManager usbManager) { + mContext = context; + mListener = listener; + mUsbManager = usbManager; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mCloseGuard.open("release"); + mHandler = new UsbStateHandler(mHandlerThread.getLooper()); + mUsbStateBroadcastReceiver = new UsbDeviceBroadcastReceiver(); + } + + public void init() { + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + mContext.registerReceiver(mUsbStateBroadcastReceiver, filter); + } + + public void release() { + mCloseGuard.close(); + mContext.unregisterReceiver(mUsbStateBroadcastReceiver); + synchronized (mUsbConnectionChangeWait) { + mShouldQuit = true; + mUsbConnectionChangeWait.notifyAll(); + } + mHandlerThread.quit(); + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + release(); + } finally { + super.finalize(); + } + } + + public void startDeviceReset(UsbDevice device) { + mHandler.requestDeviceReset(device); + } + + public void startAoap(AoapSwitchRequest request) { + mHandler.requestAoap(request); + } + + private void doHandleDeviceReset(UsbDevice device) { + boolean isInAoap = AoapInterface.isDeviceInAoapMode(device); + UsbDevice completedDevice = null; + if (isInAoap) { + completedDevice = resetUsbDeviceAndConfirmModeChange(device); + } else { + UsbDeviceConnection conn = openConnection(device); + if (conn == null) { + throw new RuntimeException("cannot open conneciton for device: " + device); + } else { + try { + if (!conn.resetDevice()) { + throw new RuntimeException("resetDevice failed for devie: " + device); + } else { + completedDevice = device; + } + } finally { + conn.close(); + } + } + } + mListener.onDeviceResetComplete(completedDevice); + } + + private void doHandleAoapStart(AoapSwitchRequest request) { + UsbDevice device = request.device; + boolean isInAoap = AoapInterface.isDeviceInAoapMode(device); + if (isInAoap) { + device = resetUsbDeviceAndConfirmModeChange(device); + if (device == null) { + mListener.onAoapStartComplete(null); + return; + } + } + UsbDeviceConnection connection = openConnection(device); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER, + request.manufacturer); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL, + request.model); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION, + request.description); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION, + request.version); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, request.uri); + AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL, request.serial); + AoapInterface.sendAoapStart(connection); + device = resetUsbDeviceAndConfirmModeChange(device); + if (device == null) { + mListener.onAoapStartComplete(null); + } + if (!AoapInterface.isDeviceInAoapMode(device)) { + Log.w(TAG, "Device not in AOAP mode after switching: " + device); + mListener.onAoapStartComplete(device); + } + mListener.onAoapStartComplete(device); + } + + private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) { + int retry = 0; + boolean removalDetected = false; + while (retry < MAX_USB_STATE_CHANGE_WAIT) { + UsbDeviceConnection connNow = openConnection(device); + if (connNow == null) { + removalDetected = true; + break; + } + connNow.resetDevice(); + connNow.close(); + synchronized (mUsbConnectionChangeWait) { + try { + mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_WAIT_TIMEOUT_MS); + } catch (InterruptedException e) { + break; + } + if (mShouldQuit) { + return null; + } + if (isDeviceRemovedLocked(device)) { + removalDetected = true; + break; + } + } + retry++; + } + if (!removalDetected) { + Log.w(TAG, "resetDevice failed for device, device still in the same mode: " + device); + return null; + } + retry = 0; + UsbDevice newlyAttached = null; + while (retry < MAX_USB_STATE_CHANGE_WAIT) { + synchronized (mUsbConnectionChangeWait) { + try { + mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_WAIT_TIMEOUT_MS); + } catch (InterruptedException e) { + break; + } + if (mShouldQuit) { + return null; + } + newlyAttached = checkDeviceAttachedLocked(device); + } + if (newlyAttached != null) { + break; + } + retry++; + } + if (newlyAttached == null) { + Log.w(TAG, "resetDevice failed for device, device disconnected: " + device); + return null; + } + return newlyAttached; + } + + private boolean isDeviceRemovedLocked(UsbDevice device) { + for (UsbDevice removed : mDevicesRemoved) { + if (UsbUtil.isDevicesMatching(device, removed)) { + mDevicesRemoved.clear(); + return true; + } + } + mDevicesRemoved.clear(); + return false; + } + + private UsbDevice checkDeviceAttachedLocked(UsbDevice device) { + for (UsbDevice attached : mDevicesAdded) { + if (UsbUtil.isTheSameDevice(device, attached)) { + mDevicesAdded.clear(); + return attached; + } + } + mDevicesAdded.clear(); + return null; + } + + public UsbDeviceConnection openConnection(UsbDevice device) { + mUsbManager.grantPermission(device); + return mUsbManager.openDevice(device); + } + + private void handleUsbDeviceAttached(UsbDevice device) { + synchronized (mUsbConnectionChangeWait) { + mDevicesAdded.add(device); + mUsbConnectionChangeWait.notifyAll(); + } + } + + private void handleUsbDeviceDetached(UsbDevice device) { + synchronized (mUsbConnectionChangeWait) { + mDevicesRemoved.add(device); + mUsbConnectionChangeWait.notifyAll(); + } + } + + private class UsbStateHandler extends Handler { + private final int MSG_RESET_DEVICE = 1; + private final int MSG_AOAP = 2; + + private UsbStateHandler(Looper looper) { + super(looper); + } + + private void requestDeviceReset(UsbDevice device) { + Message msg = obtainMessage(MSG_RESET_DEVICE, device); + sendMessage(msg); + } + + private void requestAoap(AoapSwitchRequest request) { + Message msg = obtainMessage(MSG_AOAP, request); + sendMessage(msg); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_RESET_DEVICE: + doHandleDeviceReset((UsbDevice) msg.obj); + break; + case MSG_AOAP: + doHandleAoapStart((AoapSwitchRequest) msg.obj); + break; + } + } + } + + private class UsbDeviceBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { + UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceDetached(device); + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceAttached(device); + } + } + } + + public static class AoapSwitchRequest { + public final UsbDevice device; + public final String manufacturer; + public final String model; + public final String description; + public final String version; + public final String uri; + public final String serial; + + public AoapSwitchRequest(UsbDevice device, String manufacturer, String model, + String description, String version, String uri, String serial) { + this.device = device; + this.manufacturer = manufacturer; + this.model = model; + this.description = description; + this.version = version; + this.uri = uri; + this.serial = serial; + } + } +} diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java new file mode 100644 index 000000000000..2d9226ff4246 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.externalmanagementtest; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.net.nsd.NsdManager.DiscoveryListener; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; + +import com.android.hardware.usb.externalmanagementtest.UsbDeviceStateController.AoapSwitchRequest; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +public class UsbHostManagementActivity extends Activity + implements UsbDeviceStateController.UsbDeviceStateListener { + + private static final String TAG = UsbHostManagementActivity.class.getSimpleName(); + + private static final String AOAP_APP_PACKAGE_NAME = "com.android.hardware.usb.aoaphosttest"; + private static final String AOAP_APP_ACTIVITY_NAME = + "com.android.hardware.usb.aoaphosttest.UsbAoapHostTestActivity"; + + private TextView mDeviceInfoText; + private Button mStartAoapButton; + private Button mStartAoapActivityButton; + private TextView mAoapAppLog; + private Button mResetUsbButton; + private Button mFinishButton; + private UsbDevice mUsbDevice = null; + private final UsbDeviceConnectionListener mConnectionListener = + new UsbDeviceConnectionListener(); + private UsbDeviceStateController mUsbDeviceStateController; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "onCreate"); + super.onCreate(savedInstanceState); + setContentView(R.layout.host_management); + mDeviceInfoText = (TextView) findViewById(R.id.device_info_text); + mStartAoapButton = (Button) findViewById(R.id.start_aoap_button); + mStartAoapActivityButton = (Button) findViewById(R.id.start_aoap_activity_button); + mAoapAppLog = (TextView) findViewById(R.id.aoap_apps_text); + mResetUsbButton = (Button) findViewById(R.id.reset_button); + mFinishButton = (Button) findViewById(R.id.finish_button); + + Intent intent = getIntent(); + if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + mUsbDevice = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); + } + UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + if (mUsbDevice == null) { + LinkedList<UsbDevice> devices = UsbUtil.findAllPossibleAndroidDevices(usbManager); + if (devices.size() > 0) { + mUsbDevice = devices.getLast(); + } + } + updateDevice(mUsbDevice); + + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + registerReceiver(mConnectionListener, filter); + + mStartAoapButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mUsbDevice == null) { + return; + } + startAoap(mUsbDevice); + } + }); + mStartAoapActivityButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mUsbDevice == null) { + return; + } + startAoapActivity(mUsbDevice); + } + }); + mResetUsbButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mUsbDevice == null) { + return; + } + resetDevice(mUsbDevice); + } + }); + mFinishButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mUsbDeviceStateController = new UsbDeviceStateController(this, this, usbManager); + mUsbDeviceStateController.init(); + } + + + private void startAoap(UsbDevice device) { + AoapSwitchRequest request = new AoapSwitchRequest(device, "Android", "AOAP Test App", "", + "1.0", "", ""); + mUsbDeviceStateController.startAoap(request); + } + + private void startAoapActivity(UsbDevice device) { + if (!AoapInterface.isDeviceInAoapMode(device)) { + Log.w(TAG, "Device not in AOAP mode:" + device); + return; + } + PackageManager pm = getPackageManager(); + PackageInfo pi = null; + try { + pi = pm.getPackageInfo(AOAP_APP_PACKAGE_NAME, 0); + } catch (NameNotFoundException e) { + Log.w(TAG, "AOAP Test app not found:" + AOAP_APP_PACKAGE_NAME); + } + int uid = pi.applicationInfo.uid; + UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + usbManager.grantPermission(device, uid); + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.setComponent(new ComponentName(AOAP_APP_PACKAGE_NAME, AOAP_APP_ACTIVITY_NAME)); + startActivity(intent); + } + + private void resetDevice(UsbDevice device) { + Log.i(TAG, "resetDevice"); + mUsbDeviceStateController.startDeviceReset(device); + } + + private void dumpUsbDevices() { + UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + HashMap<String, UsbDevice> devices = usbManager.getDeviceList(); + StringBuilder sb = new StringBuilder(); + sb.append("Usb devices\n"); + for (UsbDevice device : devices.values()) { + sb.append(device.toString() + "\n"); + } + Log.i(TAG, sb.toString()); + } + + @Override + protected void onDestroy() { + Log.i(TAG, "onDestroy"); + super.onDestroy(); + unregisterReceiver(mConnectionListener); + mUsbDeviceStateController.release(); + } + + private void handleUsbDeviceAttached(UsbDevice device) { + boolean deviceReplaced = false; + if (mUsbDevice == null) { + deviceReplaced = true; + } else { + UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + if (!UsbUtil.isDeviceConnected(usbManager, mUsbDevice)) { + deviceReplaced = true; + } + } + if (deviceReplaced) { + Log.i(TAG, "device attached:" + device); + updateDevice(device); + } + } + + private void handleUsbDeviceDetached(UsbDevice device) { + if (mUsbDevice != null && UsbUtil.isDevicesMatching(mUsbDevice, device)) { + Log.i(TAG, "device removed "); + updateDevice(device); + } + } + + private void updateDevice(UsbDevice device) { + mUsbDevice = device; + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mUsbDevice == null) { + mDeviceInfoText.setText("disconnected"); + } else { + mDeviceInfoText.setText(mUsbDevice.toString()); + } + } + }); + } + + @Override + public void onDeviceResetComplete(UsbDevice device) { + updateDevice(device); + } + + + @Override + public void onAoapStartComplete(UsbDevice device) { + updateDevice(device); + } + + private class UsbDeviceConnectionListener extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) { + UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceDetached(device); + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { + UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceAttached(device); + } + } + } +} diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java new file mode 100644 index 000000000000..8d0f73ed05a9 --- /dev/null +++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hardware.usb.externalmanagementtest; + +import java.util.HashMap; +import java.util.LinkedList; + +import android.content.Context; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.text.TextUtils; + +public class UsbUtil { + public static final String ADB_INTERFACE_NAME = "ADB Interface"; + public static final String AOAP_INTERFACE_NAME = "Android Accessory Interface"; + public static final String MTP_INTERFACE_NAME = "MTP"; + + public static LinkedList<UsbDevice> findAllPossibleAndroidDevices(UsbManager usbManager) { + HashMap<String, UsbDevice> devices = usbManager.getDeviceList(); + LinkedList<UsbDevice> androidDevices = null; + for (UsbDevice device : devices.values()) { + if (possiblyAndroid(device)) { + if (androidDevices == null) { + androidDevices = new LinkedList<>(); + } + androidDevices.add(device); + } + } + return androidDevices; + } + + public static boolean possiblyAndroid(UsbDevice device) { + int numInterfaces = device.getInterfaceCount(); + for (int i = 0; i < numInterfaces; i++) { + UsbInterface usbInterface = device.getInterface(i); + String interfaceName = usbInterface.getName(); + // more thorough check can be added, later + if (AOAP_INTERFACE_NAME.equals(interfaceName) || + ADB_INTERFACE_NAME.equals(interfaceName) || + MTP_INTERFACE_NAME.equals(interfaceName)) { + return true; + } + } + return false; + } + + public static boolean isTheSameDevice(UsbDevice l, UsbDevice r) { + if (TextUtils.equals(l.getManufacturerName(), r.getManufacturerName()) && + TextUtils.equals(l.getProductName(), r.getProductName()) && + TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) { + return true; + } + return false; + } + + public static boolean isDevicesMatching(UsbDevice l, UsbDevice r) { + if (l.getVendorId() == r.getVendorId() && l.getProductId() == r.getProductId() && + TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) { + return true; + } + return false; + } + + public static boolean isDeviceConnected(UsbManager usbManager, UsbDevice device) { + HashMap<String, UsbDevice> devices = usbManager.getDeviceList(); + for (UsbDevice dev : devices.values()) { + if (isDevicesMatching(dev, device)) { + return true; + } + } + return false; + } +} |