UsbManager: Enhancements for managing USB devices and accessories

When a USB device or accessory is connected, the UsbService now asks the
user which application to associate with the device or accessory.
Applications interested in devices or accessories must specify the
devices they work with via meta-data attached to their manifest.
Permission to communicate with the device is assigned when the user chooses
the activity to use for the device.
The user has the option of clicking the "always use this application" checkbox
to make the assignment automatic in the future.
The user may later clear this preference and revoke permission for an application
to have permission to communicate with the device by clicking the "Clear defaults"
button for the activity in the Manage Applications panel in Settings.

Added class UsbResolveActivity (a subclass or ResolveActivity for choosing
an activity for a USB device or accessory)

Added UsbDeviceManager, which manages the mapping between USB devices/accessories
and applications, including default applications for devices and accessories,
and manages application permissions.

Add interface to allow Settings to clear device and accessory
preferences and permissions for an application.

Remove obsolete ACCESS_USB permission.

Add new signatureOrSystem MANAGE_USB permission to allow administrating
preferences and permissions.

Moved UsbService.java to a "usb" subdirectory, along with new classes
UsbResolveActivity and UsbDeviceManager.

Change-Id: I92554381e9779e68ce380daaee4e1401fb875703
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/core/java/android/hardware/IUsbManager.aidl b/core/java/android/hardware/IUsbManager.aidl
index 6c99ab3..8086f37 100644
--- a/core/java/android/hardware/IUsbManager.aidl
+++ b/core/java/android/hardware/IUsbManager.aidl
@@ -17,6 +17,7 @@
 package android.hardware;
 
 import android.hardware.UsbAccessory;
+import android.hardware.UsbDevice;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
@@ -25,7 +26,39 @@
 {
     /* Returns a list of all currently attached USB devices */
     void getDeviceList(out Bundle devices);
+
+    /* Returns a file descriptor for communicating with the USB device.
+     * The native fd can be passed to usb_device_new() in libusbhost.
+     */
     ParcelFileDescriptor openDevice(String deviceName);
+
+    /* Returns the currently attached USB accessory */
     UsbAccessory getCurrentAccessory();
-    ParcelFileDescriptor openAccessory();
+
+    /* Returns a file descriptor for communicating with the USB accessory.
+     * This file descriptor can be used with standard Java file operations.
+     */
+    ParcelFileDescriptor openAccessory(in UsbAccessory accessory);
+
+    /* Sets the default package for a USB device
+     * (or clears it if the package name is null)
+     */
+    oneway void setDevicePackage(in UsbDevice device, String packageName);
+
+    /* Sets the default package for a USB device
+     * (or clears it if the package name is null)
+     */
+    void setAccessoryPackage(in UsbAccessory accessory, String packageName);
+
+    /* Grants permission for the given UID to access the device */
+    void grantDevicePermission(in UsbDevice device, int uid);
+
+    /* Grants permission for the given UID to access the accessory */
+    void grantAccessoryPermission(in UsbAccessory accessory, int uid);
+
+    /* Returns true if the USB manager has default preferences or permissions for the package */
+    boolean hasDefaults(String packageName, int uid);
+
+    /* Clears default preferences and permissions for the package */
+    oneway void clearDefaults(String packageName, int uid);
 }
diff --git a/core/java/android/hardware/UsbAccessory.java b/core/java/android/hardware/UsbAccessory.java
index 71672fa..15dff3e 100644
--- a/core/java/android/hardware/UsbAccessory.java
+++ b/core/java/android/hardware/UsbAccessory.java
@@ -95,6 +95,23 @@
         return mVersion;
     }
 
+    private static boolean compare(String s1, String s2) {
+        if (s1 == null) return (s2 == null);
+        return s1.equals(s2);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof UsbAccessory) {
+            UsbAccessory accessory = (UsbAccessory)obj;
+            return (compare(mManufacturer, accessory.getManufacturer()) &&
+                    compare(mModel, accessory.getModel()) &&
+                    compare(mType, accessory.getType()) &&
+                    compare(mVersion, accessory.getVersion()));
+        }
+        return false;
+    }
+
     @Override
     public String toString() {
         return "UsbAccessory[mManufacturer=" + mManufacturer +
diff --git a/core/java/android/hardware/UsbManager.java b/core/java/android/hardware/UsbManager.java
index dcfdcf4..e9a34ea 100644
--- a/core/java/android/hardware/UsbManager.java
+++ b/core/java/android/hardware/UsbManager.java
@@ -98,7 +98,7 @@
      *
      * This intent is sent when a USB accessory is detached.
      * <ul>
-      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.UsbAccessory}
+     * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.UsbAccessory}
      * for the attached accessory that was detached
      * </ul>
      */
@@ -163,14 +163,15 @@
 
     /**
      * Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} and
-     * {@link #ACTION_USB_DEVICE_DETACHED} broadcasts.
+     * {@link #ACTION_USB_DEVICE_DETACHED} broadcasts
      * containing the UsbDevice object for the device.
      */
 
     public static final String EXTRA_DEVICE = "device";
 
     /**
-     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} broadcast
+     * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} and
+     * {@link #ACTION_USB_ACCESSORY_DETACHED} broadcasts
      * containing the UsbAccessory object for the accessory.
      */
     public static final String EXTRA_ACCESSORY = "accessory";
@@ -244,7 +245,7 @@
                 return new UsbAccessory[] { accessory };
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in openAccessory" , e);
+            Log.e(TAG, "RemoteException in getAccessoryList" , e);
             return null;
         }
     }
@@ -257,7 +258,7 @@
      */
     public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
         try {
-            return mService.openAccessory();
+            return mService.openAccessory(accessory);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in openAccessory" , e);
             return null;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 841de06..fb2a72b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -68,7 +68,7 @@
 
     protected void onCreate(Bundle savedInstanceState, Intent intent,
             CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
-            boolean alwaysUseOption) {
+            boolean alwaysUseOption, boolean alwaysChoose) {
         super.onCreate(savedInstanceState);
         mPm = getPackageManager();
         intent.setComponent(null);
@@ -90,9 +90,10 @@
             mClearDefaultHint.setVisibility(View.GONE);
         }
         mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList);
-        if (mAdapter.getCount() > 1) {
+        int count = mAdapter.getCount();
+        if (count > 1 || (count == 1 && alwaysChoose)) {
             ap.mAdapter = mAdapter;
-        } else if (mAdapter.getCount() == 1) {
+        } else if (count == 1) {
             startActivity(mAdapter.intentForPosition(0));
             finish();
             return;
@@ -103,11 +104,22 @@
         setupAlert();
     }
 
+    protected void onCreate(Bundle savedInstanceState, Intent intent,
+            CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean alwaysUseOption) {
+        onCreate(savedInstanceState, intent, title, initialIntents, rList, alwaysUseOption, false);
+      }
+
     public void onClick(DialogInterface dialog, int which) {
         ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
         Intent intent = mAdapter.intentForPosition(which);
+        boolean alwaysCheck = (mAlwaysCheck != null && mAlwaysCheck.isChecked());
+        onIntentSelected(ri, intent, alwaysCheck);
+        finish();
+    }
 
-        if ((mAlwaysCheck != null) && mAlwaysCheck.isChecked()) {
+    protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
+        if (alwaysCheck) {
             // Build a reasonable intent filter, based on what matched.
             IntentFilter filter = new IntentFilter();
 
@@ -190,7 +202,6 @@
         if (intent != null) {
             startActivity(intent);
         }
-        finish();
     }
 
     private final class DisplayResolveInfo {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index beb824c..50d3fb8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -466,12 +466,13 @@
         android:label="@string/permlab_flashlight"
         android:description="@string/permdesc_flashlight" />
 
-    <!-- Allows an application to access USB devices -->
-    <permission android:name="android.permission.ACCESS_USB"
+    <!-- Allows an application to manage preferences and permissions for USB devices
+         @hide -->
+    <permission android:name="android.permission.MANAGE_USB"
         android:permissionGroup="android.permission-group.HARDWARE_CONTROLS"
-        android:protectionLevel="normal"
-        android:label="@string/permlab_accessUsb"
-        android:description="@string/permdesc_accessUsb" />
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_manageUsb"
+        android:description="@string/permdesc_manageUsb" />
 
     <!-- Allows an application to access the MTP USB kernel driver.
          For use only by the device side MTP implementation.
@@ -1372,11 +1373,18 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+
         <activity android:name="com.android.internal.app.NetInitiatedActivity"
                 android:theme="@style/Theme.Holo.Dialog.Alert"
                 android:excludeFromRecents="true">
         </activity>
 
+        <activity android:name="com.android.server.usb.UsbResolverActivity"
+            android:theme="@style/Theme.Holo.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true">
+        </activity>
+
         <service android:name="com.android.server.LoadAverageService"
                 android:exported="true" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 361bb6c..c02a3f1 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1031,9 +1031,9 @@
         the flashlight.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_accessUsb">access USB devices</string>
+    <string name="permlab_manageUsb">manage preferences and permissions for USB devices</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessUsb">Allows the application to access USB devices.</string>
+    <string name="permdesc_manageUsb">Allows the application to manage preferences and permissions for USB devices.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_accessMtp">implement MTP protocol</string>
@@ -2193,6 +2193,8 @@
     <string name="clearDefaultHintMsg">Clear default in Home Settings &gt; Applications &gt; Manage applications.</string>
     <!-- Default title for the activity chooser, when one is not given. Android allows multiple activities to perform an action.  for example, there may be many ringtone pickers installed.  A dialog is shown to the user allowing him to pick which activity should be used.  This is the title. -->
     <string name="chooseActivity">Select an action</string>
+    <!-- title for the USB activity chooser. -->
+    <string name="chooseUsbActivity">Select an application for the USB device</string>
     <!-- Text to display when there are no activities found to display in the
          activity chooser. See the "Select an action" title. -->
     <string name="noApplications">No applications can perform this action.</string>