diff options
author | Keun-young Park <keunyoung@google.com> | 2016-01-05 13:27:29 -0800 |
---|---|---|
committer | Vitalii Tomkiv <vitalit@google.com> | 2016-08-24 15:00:08 -0700 |
commit | badbbae6fa2846415778b2a152a0758acbf0eb74 (patch) | |
tree | e4fe03dfcf31c78e12f13fc16b7fb810b67bae1f | |
parent | d3ca5980f7d85e706d39af6da26686b6632f1d46 (diff) |
allow external USB host management
- Setting config_UsbDeviceConnectionHandling_component leads into
launching specified Activity whenever USB device is connected.
- This allows external Activity to manage USB device based on
its own setup and settings.
- Device access can be passed to other app with permission update
by UsbManager.grantPermission.
- added UsbDeviceConnection.resetDevice() to reset USB device connected.
This is necessary to get device out from AOAP.
- Test requires installing UsbHostExternalManagmentTestApp and
AoapTestHost to USB host, and AoapTestDevice to USB Device.
bug: 26404209
Change-Id: I8e77ddc646c15454d9b2ecf1356924cf6351fc28
28 files changed, 1648 insertions, 7 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 3e47ac723abe..31973222bf54 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -15944,6 +15944,7 @@ package android.hardware.usb { method public java.lang.String getSerial(); method public boolean releaseInterface(android.hardware.usb.UsbInterface); method public android.hardware.usb.UsbRequest requestWait(); + method public boolean resetDevice(); method public boolean setConfiguration(android.hardware.usb.UsbConfiguration); method public boolean setInterface(android.hardware.usb.UsbInterface); } diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 6e4c9de1c8ef..fafe116df606 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -17,6 +17,7 @@ package android.hardware.usb; import android.app.PendingIntent; +import android.content.ComponentName; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbPort; @@ -116,4 +117,7 @@ interface IUsbManager /* Sets the port's current role. */ void setPortRoles(in String portId, int powerRole, int dataRole); + + /* Sets USB device connection handler. */ + void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); } diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index c062b3a31152..54fea52624c0 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -16,6 +16,7 @@ package android.hardware.usb; +import android.annotation.SystemApi; import android.os.ParcelFileDescriptor; import java.io.FileDescriptor; @@ -215,8 +216,20 @@ public class UsbDeviceConnection { } /** + * Reset USB port for the connected device. + * + * @return true if reset succeeds. + * + * @hide + */ + @SystemApi + public boolean resetDevice() { + return native_reset_device(); + } + + /** * Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation - * Note that this may return requests queued on multiple + * Note that this may return requests queued on multiple * {@link android.hardware.usb.UsbEndpoint}s. * When multiple endpoints are in use, {@link android.hardware.usb.UsbRequest#getEndpoint} and * {@link android.hardware.usb.UsbRequest#getClientData} can be useful in determining @@ -263,4 +276,5 @@ public class UsbDeviceConnection { int offset, int length, int timeout); private native UsbRequest native_request_wait(); private native String native_get_serial(); + private native boolean native_reset_device(); } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 629db06a30e3..df4785ee45d9 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -19,7 +19,9 @@ package android.hardware.usb; import com.android.internal.util.Preconditions; +import android.annotation.Nullable; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; @@ -469,8 +471,20 @@ public class UsbManager { * {@hide} */ public void grantPermission(UsbDevice device) { + grantPermission(device, Process.myUid()); + } + + /** + * Grants permission for USB device to given uid without showing system dialog. + * Only system components can call this function. + * @param device to request permissions for + * @uid uid to give permission + * + * {@hide} + */ + public void grantPermission(UsbDevice device, int uid) { try { - mService.grantDevicePermission(device, Process.myUid()); + mService.grantDevicePermission(device, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -488,11 +502,9 @@ public class UsbManager { try { int uid = mContext.getPackageManager() .getPackageUidAsUser(packageName, mContext.getUserId()); - mService.grantDevicePermission(device, uid); + grantPermission(device, uid); } catch (NameNotFoundException e) { Log.e(TAG, "Package " + packageName + " not found.", e); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } @@ -631,6 +643,26 @@ public class UsbManager { } } + /** + * Sets the component that will handle USB device connection. + * <p> + * Setting component allows to specify external USB host manager to handle use cases, where + * selection dialog for an activity that will handle USB device is undesirable. + * Only system components can call this function, as it requires the MANAGE_USB permission. + * + * @param usbDeviceConnectionHandler The component to handle usb connections, + * {@code null} to unset. + * + * {@hide} + */ + public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { + try { + mService.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public static String addFunction(String functions, String function) { if (USB_FUNCTION_NONE.equals(functions)) { diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp index 1ba9fc58800c..f899c00ec100 100644 --- a/core/jni/android_hardware_UsbDeviceConnection.cpp +++ b/core/jni/android_hardware_UsbDeviceConnection.cpp @@ -246,6 +246,18 @@ android_hardware_UsbDeviceConnection_get_serial(JNIEnv *env, jobject thiz) return result; } +static jboolean +android_hardware_UsbDeviceConnection_reset_device(JNIEnv *env, jobject thiz) +{ + struct usb_device* device = get_device_from_object(env, thiz); + if (!device) { + ALOGE("device is closed in native_reset_device"); + return JNI_FALSE; + } + int ret = usb_device_reset(device); + return (ret == 0) ? JNI_TRUE : JNI_FALSE; +} + static const JNINativeMethod method_table[] = { {"native_open", "(Ljava/lang/String;Ljava/io/FileDescriptor;)Z", (void *)android_hardware_UsbDeviceConnection_open}, @@ -264,6 +276,7 @@ static const JNINativeMethod method_table[] = { (void *)android_hardware_UsbDeviceConnection_request_wait}, { "native_get_serial", "()Ljava/lang/String;", (void*)android_hardware_UsbDeviceConnection_get_serial }, + {"native_reset_device","()Z", (void *)android_hardware_UsbDeviceConnection_reset_device}, }; int register_android_hardware_UsbDeviceConnection(JNIEnv *env) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ec93e47060f0..15b32c75010d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1829,6 +1829,11 @@ Do not set this to true for production devices. Doing so will cause you to fail CTS. --> <bool name="config_disableUsbPermissionDialogs">false</bool> + <!-- Activity to handle Usb Device connection in USB Host side. Keeping it to null value will + lead into handling it inside system using Intent resolution. Non-null contents will have + format of package-name/ActivityClassName. --> + <string name="config_UsbDeviceConnectionHandling_component" translatable="false">@null</string> + <!-- Minimum span needed to begin a touch scaling gesture. If the span is equal to or greater than this size, a scaling gesture will begin, where supported. (See android.view.ScaleGestureDetector) diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 166862fc6cb8..e219b766e266 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1866,6 +1866,7 @@ <java-symbol type="string" name="usb_ptp_notification_title" /> <java-symbol type="string" name="usb_midi_notification_title" /> <java-symbol type="string" name="usb_supplying_notification_title" /> + <java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" /> <java-symbol type="string" name="vpn_text" /> <java-symbol type="string" name="vpn_text_long" /> <java-symbol type="string" name="vpn_title" /> diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 965341e418ba..1d850e106cf5 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; @@ -24,6 +26,7 @@ import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -63,11 +66,20 @@ public class UsbHostManager { @GuardedBy("mLock") private UsbSettingsManager mCurrentSettings; + @GuardedBy("mLock") + private ComponentName mUsbDeviceConnectionHandler; + public UsbHostManager(Context context, UsbAlsaManager alsaManager) { mContext = context; mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); mUsbAlsaManager = alsaManager; + String deviceConnectionHandler = context.getResources().getString( + com.android.internal.R.string.config_UsbDeviceConnectionHandling_component); + if (!TextUtils.isEmpty(deviceConnectionHandler)) { + setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( + deviceConnectionHandler)); + } } public void setCurrentSettings(UsbSettingsManager settings) { @@ -82,6 +94,18 @@ public class UsbHostManager { } } + public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { + synchronized (mLock) { + mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; + } + } + + private @Nullable ComponentName getUsbDeviceConnectionHandler() { + synchronized (mLock) { + return mUsbDeviceConnectionHandler; + } + } + private boolean isBlackListed(String deviceName) { int count = mHostBlacklist.length; for (int i = 0; i < count; i++) { @@ -219,10 +243,17 @@ public class UsbHostManager { synchronized (mLock) { if (mNewDevice != null) { mNewDevice.setConfigurations( - mNewConfigurations.toArray(new UsbConfiguration[mNewConfigurations.size()])); + mNewConfigurations.toArray( + new UsbConfiguration[mNewConfigurations.size()])); mDevices.put(mNewDevice.getDeviceName(), mNewDevice); Slog.d(TAG, "Added device " + mNewDevice); - getCurrentSettings().deviceAttached(mNewDevice); + ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); + if (usbDeviceConnectionHandler == null) { + getCurrentSettings().deviceAttached(mNewDevice); + } else { + getCurrentSettings().deviceAttachedForFixedHandler(mNewDevice, + usbDeviceConnectionHandler); + } mUsbAlsaManager.usbDeviceAdded(mNewDevice); } else { Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); @@ -292,6 +323,9 @@ public class UsbHostManager { for (String name : mDevices.keySet()) { pw.println(" " + name + ": " + mDevices.get(name)); } + if (mUsbDeviceConnectionHandler != null) { + pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); + } } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index d6dbe90584f5..81ac2dd966bf 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -19,6 +19,7 @@ package com.android.server.usb; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -385,6 +386,14 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + if (mHostManager != null) { + mHostManager.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java index de9ede397c13..6b9acf29672c 100644 --- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java @@ -760,6 +760,31 @@ class UsbSettingsManager { resolveActivity(intent, matches, defaultPackage, device, null); } + public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) { + final Intent intent = createDeviceAttachedIntent(device); + + // Send broadcast to running activity with registered intent + mUserContext.sendBroadcast(intent); + + ApplicationInfo appInfo; + try { + appInfo = mPackageManager.getApplicationInfo(component.getPackageName(), 0); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Default USB handling package not found: " + component.getPackageName()); + return; + } + + grantDevicePermission(device, appInfo.uid); + + Intent activityIntent = new Intent(intent); + activityIntent.setComponent(component); + try { + mUserContext.startActivityAsUser(activityIntent, mUser); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "unable to start activity " + activityIntent); + } + } + public void deviceDetached(UsbDevice device) { // clear temporary permissions for the device mDevicePermissionMap.remove(device.getDeviceName()); 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; + } +} |