diff options
17 files changed, 935 insertions, 232 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index fb9335b..46ad64c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1158,7 +1158,7 @@ class ContextImpl extends Context { if (mUsbManager == null) { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); - mUsbManager = new UsbManager(service); + mUsbManager = new UsbManager(this, service); } } return mUsbManager; diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 9be7e77..ffe557c 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -16,6 +16,7 @@ package android.hardware.usb; +import android.app.PendingIntent; import android.hardware.usb.UsbAccessory; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -36,12 +37,22 @@ interface IUsbManager */ void setAccessoryPackage(in UsbAccessory accessory, String packageName); + /* Returns true if the caller has permission to access the accessory. */ + boolean hasAccessoryPermission(in UsbAccessory accessory); + + /* Requests permission for the given package to access the accessory. + * Will display a system dialog to query the user if permission + * had not already been given. Result is returned via pi. + */ + void requestAccessoryPermission(in UsbAccessory accessory, String packageName, + in PendingIntent pi); + /* 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); + boolean hasDefaults(String packageName); /* Clears default preferences and permissions for the package */ - oneway void clearDefaults(String packageName, int uid); + void clearDefaults(String packageName); } diff --git a/core/java/android/hardware/usb/UsbAccessory.java b/core/java/android/hardware/usb/UsbAccessory.java index a7e953d..41aed99 100644 --- a/core/java/android/hardware/usb/UsbAccessory.java +++ b/core/java/android/hardware/usb/UsbAccessory.java @@ -31,18 +31,21 @@ public class UsbAccessory implements Parcelable { private final String mManufacturer; private final String mModel; - private final String mType; + private final String mDescription; private final String mVersion; + private final String mUri; /** * UsbAccessory should only be instantiated by UsbService implementation * @hide */ - public UsbAccessory(String manufacturer, String model, String type, String version) { + public UsbAccessory(String manufacturer, String model, String description, + String version, String uri) { mManufacturer = manufacturer; mModel = model; - mType = type; + mDescription = description; mVersion = version; + mUri = uri; } /** @@ -52,8 +55,9 @@ public class UsbAccessory implements Parcelable { public UsbAccessory(String[] strings) { mManufacturer = strings[0]; mModel = strings[1]; - mType = strings[2]; + mDescription = strings[2]; mVersion = strings[3]; + mUri = strings[4]; } /** @@ -75,12 +79,12 @@ public class UsbAccessory implements Parcelable { } /** - * Returns the type of the accessory. + * Returns a user visible description of the accessory. * - * @return the accessory type + * @return the accessory description */ - public String getType() { - return mType; + public String getDescription() { + return mDescription; } /** @@ -92,6 +96,17 @@ public class UsbAccessory implements Parcelable { return mVersion; } + /** + * Returns the URI for the accessory. + * This is an optional URI that might show information about the accessory + * or provide the option to download an application for the accessory + * + * @return the accessory URI + */ + public String getUri() { + return mUri; + } + private static boolean compare(String s1, String s2) { if (s1 == null) return (s2 == null); return s1.equals(s2); @@ -103,18 +118,29 @@ public class UsbAccessory implements Parcelable { UsbAccessory accessory = (UsbAccessory)obj; return (compare(mManufacturer, accessory.getManufacturer()) && compare(mModel, accessory.getModel()) && - compare(mType, accessory.getType()) && - compare(mVersion, accessory.getVersion())); + compare(mDescription, accessory.getDescription()) && + compare(mVersion, accessory.getVersion()) && + compare(mUri, accessory.getUri())); } return false; } @Override + public int hashCode() { + return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ + (mModel == null ? 0 : mModel.hashCode()) ^ + (mDescription == null ? 0 : mDescription.hashCode()) ^ + (mVersion == null ? 0 : mVersion.hashCode()) ^ + (mUri == null ? 0 : mUri.hashCode())); + } + + @Override public String toString() { return "UsbAccessory[mManufacturer=" + mManufacturer + ", mModel=" + mModel + - ", mType=" + mType + - ", mVersion=" + mVersion + "]"; + ", mDescription=" + mDescription + + ", mVersion=" + mVersion + + ", mUri=" + mUri + "]"; } public static final Parcelable.Creator<UsbAccessory> CREATOR = @@ -122,9 +148,10 @@ public class UsbAccessory implements Parcelable { public UsbAccessory createFromParcel(Parcel in) { String manufacturer = in.readString(); String model = in.readString(); - String type = in.readString(); + String description = in.readString(); String version = in.readString(); - return new UsbAccessory(manufacturer, model, type, version); + String uri = in.readString(); + return new UsbAccessory(manufacturer, model, description, version, uri); } public UsbAccessory[] newArray(int size) { @@ -139,7 +166,8 @@ public class UsbAccessory implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mManufacturer); parcel.writeString(mModel); - parcel.writeString(mType); + parcel.writeString(mDescription); parcel.writeString(mVersion); + parcel.writeString(mUri); } } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index a350274..ea820f9 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -17,6 +17,8 @@ package android.hardware.usb; +import android.app.PendingIntent; +import android.content.Context; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -75,7 +77,7 @@ public class UsbManager { * * This intent is sent when a USB accessory is detached. * <ul> - * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory} + * <li> {@link #EXTRA_ACCESSORY} containing the {@link UsbAccessory} * for the attached accessory that was detached * </ul> */ @@ -145,12 +147,22 @@ public class UsbManager { */ public static final String EXTRA_ACCESSORY = "accessory"; - private IUsbManager mService; + /** + * Name of extra added to the {@link android.app.PendingIntent} + * passed into {@link #requestPermission(UsbDevice, PendingIntent)} + * or {@link #requestPermission(UsbAccessory, PendingIntent)} + * containing a boolean value indicating whether the user granted permission or not. + */ + public static final String EXTRA_PERMISSION_GRANTED = "permission"; + + private final Context mContext; + private final IUsbManager mService; /** * {@hide} */ - public UsbManager(IUsbManager service) { + public UsbManager(Context context, IUsbManager service) { + mContext = context; mService = service; } @@ -169,7 +181,7 @@ public class UsbManager { return new UsbAccessory[] { accessory }; } } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getAccessoryList" , e); + Log.e(TAG, "RemoteException in getAccessoryList", e); return null; } } @@ -184,11 +196,55 @@ public class UsbManager { try { return mService.openAccessory(accessory); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in openAccessory" , e); + Log.e(TAG, "RemoteException in openAccessory", e); return null; } } + /** + * Returns true if the caller has permission to access the accessory. + * Permission might have been granted temporarily via + * {@link #requestPermission(UsbAccessory, PendingIntent)} or + * by the user choosing the caller as the default application for the accessory. + * + * @param accessory to check permissions for + * @return true if caller has permission + */ + public boolean hasPermission(UsbAccessory accessory) { + try { + return mService.hasAccessoryPermission(accessory); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in hasPermission", e); + return false; + } + } + + /** + * Requests temporary permission for the given package to access the accessory. + * This may result in a system dialog being displayed to the user + * if permission had not already been granted. + * Success or failure is returned via the {@link android.app.PendingIntent} pi. + * If successful, this grants the caller permission to access the accessory only + * until the device is disconnected. + * + * The following extras will be added to pi: + * <ul> + * <li> {@link #EXTRA_ACCESSORY} containing the accessory passed into this call + * <li> {@link #EXTRA_PERMISSION_GRANTED} containing boolean indicating whether + * permission was granted by the user + * </ul> + * + * @param accessory to request permissions for + * @param pi PendingIntent for returning result + */ + public void requestPermission(UsbAccessory accessory, PendingIntent pi) { + try { + mService.requestAccessoryPermission(accessory, mContext.getPackageName(), pi); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in requestPermission", e); + } + } + private static File getFunctionEnableFile(String function) { return new File("/sys/class/usb_composite/" + function + "/enable"); } diff --git a/libs/usb/src/com/android/future/usb/UsbAccessory.java b/libs/usb/src/com/android/future/usb/UsbAccessory.java index cdd2b73..3d0707f 100644 --- a/libs/usb/src/com/android/future/usb/UsbAccessory.java +++ b/libs/usb/src/com/android/future/usb/UsbAccessory.java @@ -23,14 +23,16 @@ public final class UsbAccessory { private final String mManufacturer; private final String mModel; - private final String mType; + private final String mDescription; private final String mVersion; + private final String mUri; /* package */ UsbAccessory(android.hardware.usb.UsbAccessory accessory) { mManufacturer = accessory.getManufacturer(); mModel = accessory.getModel(); - mType = accessory.getType(); + mDescription = accessory.getDescription(); mVersion = accessory.getVersion(); + mUri = accessory.getUri(); } /** @@ -52,12 +54,12 @@ public final class UsbAccessory { } /** - * Returns the type of the accessory. + * Returns a user visible description of the accessory. * - * @return the accessory type + * @return the accessory description */ - public String getType() { - return mType; + public String getDescription() { + return mDescription; } /** @@ -69,6 +71,17 @@ public final class UsbAccessory { return mVersion; } + /** + * Returns the URI for the accessory. + * This is an optional URI that might show information about the accessory + * or provide the option to download an application for the accessory + * + * @return the accessory URI + */ + public String getUri() { + return mUri; + } + private static boolean compare(String s1, String s2) { if (s1 == null) return (s2 == null); return s1.equals(s2); @@ -80,17 +93,28 @@ public final class UsbAccessory { UsbAccessory accessory = (UsbAccessory)obj; return (compare(mManufacturer, accessory.getManufacturer()) && compare(mModel, accessory.getModel()) && - compare(mType, accessory.getType()) && - compare(mVersion, accessory.getVersion())); + compare(mDescription, accessory.getDescription()) && + compare(mVersion, accessory.getVersion()) && + compare(mUri, accessory.getUri())); } return false; } @Override + public int hashCode() { + return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ + (mModel == null ? 0 : mModel.hashCode()) ^ + (mDescription == null ? 0 : mDescription.hashCode()) ^ + (mVersion == null ? 0 : mVersion.hashCode()) ^ + (mUri == null ? 0 : mUri.hashCode())); + } + + @Override public String toString() { return "UsbAccessory[mManufacturer=" + mManufacturer + ", mModel=" + mModel + - ", mType=" + mType + - ", mVersion=" + mVersion + "]"; + ", mDescription=" + mDescription + + ", mVersion=" + mVersion + + ", mUri=" + mUri + "]"; } } diff --git a/libs/usb/src/com/android/future/usb/UsbManager.java b/libs/usb/src/com/android/future/usb/UsbManager.java index f74b291..840e1e3 100644 --- a/libs/usb/src/com/android/future/usb/UsbManager.java +++ b/libs/usb/src/com/android/future/usb/UsbManager.java @@ -17,6 +17,7 @@ package com.android.future.usb; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.hardware.usb.IUsbManager; @@ -55,28 +56,39 @@ public class UsbManager { public static final String ACTION_USB_ACCESSORY_DETACHED = "android.hardware.usb.action.USB_ACCESSORY_DETACHED"; + /** + * Name of extra added to the {@link android.app.PendingIntent} + * passed into {#requestPermission} or {#requestPermission} + * containing a boolean value indicating whether the user granted permission or not. + */ + public static final String EXTRA_PERMISSION_GRANTED = "permission"; + + private final Context mContext; private final IUsbManager mService; - private UsbManager(IUsbManager service) { + private UsbManager(Context context, IUsbManager service) { + mContext = context; mService = service; } /** * Returns a new instance of this class. * + * @param context the caller's {@link android.content.Context} * @return UsbManager instance. */ - public static UsbManager getInstance() { + public static UsbManager getInstance(Context context) { IBinder b = ServiceManager.getService(Context.USB_SERVICE); - return new UsbManager(IUsbManager.Stub.asInterface(b)); + return new UsbManager(context, IUsbManager.Stub.asInterface(b)); } /** * Returns the {@link com.google.android.usb.UsbAccessory} for * a {@link #ACTION_USB_ACCESSORY_ATTACHED} or {@link #ACTION_USB_ACCESSORY_ATTACHED} - * broadcast Intent + * broadcast Intent. This can also be used to retrieve the accessory from the result + * of a call to {#requestPermission}. * - * @return UsbAccessory for the broadcast. + * @return UsbAccessory for the intent. */ public static UsbAccessory getAccessory(Intent intent) { android.hardware.usb.UsbAccessory accessory = @@ -118,10 +130,54 @@ public class UsbManager { try { return mService.openAccessory(new android.hardware.usb.UsbAccessory( accessory.getManufacturer(),accessory.getModel(), - accessory.getType(), accessory.getVersion())); + accessory.getDescription(), accessory.getVersion(), accessory.getUri())); } catch (RemoteException e) { Log.e(TAG, "RemoteException in openAccessory" , e); return null; } } + + /** + * Returns true if the caller has permission to access the accessory. + * Permission might have been granted temporarily via + * {@link #requestPermission(android.hardware.usb.UsbAccessory} or + * by the user choosing the caller as the default application for the accessory. + * + * @param accessory to check permissions for + * @return true if caller has permission + */ + public boolean hasPermission(UsbAccessory accessory) { + try { + return mService.hasAccessoryPermission(new android.hardware.usb.UsbAccessory( + accessory.getManufacturer(),accessory.getModel(), + accessory.getDescription(), accessory.getVersion(), accessory.getUri())); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in hasPermission", e); + return false; + } + } + + /** + * Requests temporary permission for the given package to access the accessory. + * This may result in a system dialog being displayed to the user + * if permission had not already been granted. + * Success or failure is returned via the {@link android.app.PendingIntent} pi. + * The boolean extra {@link #EXTRA_PERMISSION_GRANTED} will be attached to the + * PendingIntent to indicate success or failure. + * If successful, this grants the caller permission to access the device only + * until the device is disconnected. + * + * @param accessory to request permissions for + * @param pi PendingIntent for returning result + */ + public void requestPermission(UsbAccessory accessory, PendingIntent pi) { + try { + mService.requestAccessoryPermission(new android.hardware.usb.UsbAccessory( + accessory.getManufacturer(),accessory.getModel(), + accessory.getDescription(), accessory.getVersion(), accessory.getUri()), + mContext.getPackageName(), pi); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in requestPermission", e); + } + } } diff --git a/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c b/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c index 94cc0ce..3c0de69 100644 --- a/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c +++ b/libs/usb/tests/AccessoryChat/accessorychat/accessorychat.c @@ -133,10 +133,19 @@ static int usb_device_added(const char *devname, void* client_data) { } else { printf("Found possible android device - attempting to switch to accessory mode\n"); + uint16_t protocol; + ret = usb_device_control_transfer(device, USB_DIR_IN | USB_TYPE_VENDOR, + ACCESSORY_GET_PROTOCOL, 0, 0, &protocol, sizeof(protocol), 0); + if (ret == 2) + printf("device supports protocol version %d\n", protocol); + else + fprintf(stderr, "failed to read protocol version\n"); + send_string(device, ACCESSORY_STRING_MANUFACTURER, "Google, Inc."); send_string(device, ACCESSORY_STRING_MODEL, "AccessoryChat"); - send_string(device, ACCESSORY_STRING_TYPE, "Sample Program"); + send_string(device, ACCESSORY_STRING_DESCRIPTION, "Sample Program"); send_string(device, ACCESSORY_STRING_VERSION, "1.0"); + send_string(device, ACCESSORY_STRING_URI, "http://www.android.com"); ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, ACCESSORY_START, 0, 0, 0, 0, 0); diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java index 5cf02c7..f9a5bf4 100644 --- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java +++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java @@ -17,9 +17,11 @@ package com.android.accessorychat; 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.os.Bundle; import android.os.Handler; import android.os.Message; @@ -42,18 +44,47 @@ import java.io.IOException; public class AccessoryChat extends Activity implements Runnable, TextView.OnEditorActionListener { private static final String TAG = "AccessoryChat"; - TextView mLog; - EditText mEditText; - ParcelFileDescriptor mFileDescriptor; - FileInputStream mInputStream; - FileOutputStream mOutputStream; + + private static final String ACTION_USB_PERMISSION = + "com.android.accessorychat.action.USB_PERMISSION"; + + private TextView mLog; + private EditText mEditText; + private ParcelFileDescriptor mFileDescriptor; + private FileInputStream mInputStream; + private FileOutputStream mOutputStream; + private UsbManager mUsbManager; + private PendingIntent mPermissionIntent; + private boolean mPermissionRequestPending; private static final int MESSAGE_LOG = 1; + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_USB_PERMISSION.equals(intent.getAction())) { + synchronized (this) { + UsbAccessory accessory = UsbManager.getAccessory(intent); + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + openAccessory(accessory); + } else { + Log.d(TAG, "permission denied for accessory " + accessory); + } + mPermissionRequestPending = false; + } + } + } + }; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mUsbManager = UsbManager.getInstance(this); + mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); + IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); + registerReceiver(mUsbReceiver, filter); + setContentView(R.layout.accessory_chat); mLog = (TextView)findViewById(R.id.log); mEditText = (EditText)findViewById(R.id.message); @@ -66,21 +97,20 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit Intent intent = getIntent(); Log.d(TAG, "intent: " + intent); - UsbManager manager = UsbManager.getInstance(); - UsbAccessory[] accessories = manager.getAccessoryList(); + UsbAccessory[] accessories = mUsbManager.getAccessoryList(); UsbAccessory accessory = (accessories == null ? null : accessories[0]); if (accessory != null) { - mFileDescriptor = manager.openAccessory(accessory); - if (mFileDescriptor != null) { - FileDescriptor fd = mFileDescriptor.getFileDescriptor(); - mInputStream = new FileInputStream(fd); - mOutputStream = new FileOutputStream(fd); - Thread thread = new Thread(null, this, "AccessoryChat"); - thread.start(); + if (mUsbManager.hasPermission(accessory)) { + openAccessory(accessory); } else { - Log.d(TAG, "openAccessory fail"); + synchronized (mUsbReceiver) { + if (!mPermissionRequestPending) { + mUsbManager.requestPermission(accessory, mPermissionIntent); + mPermissionRequestPending = true; + } + } } - } else { + } else { Log.d(TAG, "mAccessory is null"); } } @@ -100,9 +130,24 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit @Override public void onDestroy() { + unregisterReceiver(mUsbReceiver); super.onDestroy(); } + private void openAccessory(UsbAccessory accessory) { + mFileDescriptor = mUsbManager.openAccessory(accessory); + if (mFileDescriptor != null) { + FileDescriptor fd = mFileDescriptor.getFileDescriptor(); + mInputStream = new FileInputStream(fd); + mOutputStream = new FileOutputStream(fd); + Thread thread = new Thread(null, this, "AccessoryChat"); + thread.start(); + Log.d(TAG, "openAccessory succeeded"); + } else { + Log.d(TAG, "openAccessory fail"); + } + } + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE && mOutputStream != null) { try { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7995869..571729b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -5,6 +5,7 @@ > <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <uses-permission android:name="android.permission.MANAGE_USB" /> <application android:persistent="true" @@ -25,5 +26,31 @@ android:excludeFromRecents="true"> </activity> + <!-- started from UsbDeviceSettingsManager --> + <activity android:name=".usb.UsbPermissionActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@*android:style/Theme.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + + <!-- started from UsbDeviceSettingsManager --> + <activity android:name=".usb.UsbResolverActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@*android:style/Theme.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + + <!-- started from UsbDeviceSettingsManager --> + <activity android:name=".usb.UsbAccessoryUriActivity" + android:exported="true" + android:permission="android.permission.MANAGE_USB" + android:theme="@*android:style/Theme.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> </application> </manifest> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ba3a3d1..3e548a2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -51,4 +51,19 @@ power usage activity to find out what drained the battery. --> <string name="battery_low_why">Battery use</string> + <!-- Prompt for the USB accessory permission dialog [CHAR LIMIT=80] --> + <string name="usb_accessory_permission_prompt">Allow the application %1$s to access the USB accessory?</string> + + <!-- Prompt for the USB accessory URI dialog [CHAR LIMIT=80] --> + <string name="usb_accessory_uri_prompt">Additional information for this device may be found at: %1$s</string> + + <!-- Title for USB accessory dialog. Used when the name of the accessory cannot be determined. [CHAR LIMIT=50] --> + <string name="title_usb_accessory">USB accessory</string> + + <!-- View button label for USB dialogs. [CHAR LIMIT=15] --> + <string name="label_view">View</string> + + <!-- Ignore button label for USB dialogs. [CHAR LIMIT=15] --> + <string name="label_ignore">Ignore</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java new file mode 100644 index 0000000..eefb1c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 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.systemui.usb; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import com.android.systemui.R; + +/** + * If the attached USB accessory has a URL associated with it, and that URL is valid, + * show this dialog to the user to allow them to optionally visit that URL for more + * information or software downloads. + * Otherwise (no valid URL) this activity does nothing at all, finishing immediately. + */ +public class UsbAccessoryUriActivity extends AlertActivity + implements DialogInterface.OnClickListener { + + private static final String TAG = "UsbAccessoryUriActivity"; + + private UsbAccessory mAccessory; + private Uri mUri; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + Intent intent = getIntent(); + mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + String uriString = intent.getStringExtra("uri"); + mUri = (uriString == null ? null : Uri.parse(uriString)); + + // sanity check before displaying dialog + if (mUri == null) { + Log.e(TAG, "could not parse Uri " + uriString); + finish(); + return; + } + String scheme = mUri.getScheme(); + if (!"http".equals(scheme) && !"https".equals(scheme)) { + Log.e(TAG, "Uri not http or https: " + mUri); + finish(); + return; + } + + final AlertController.AlertParams ap = mAlertParams; + ap.mTitle = mAccessory.getDescription(); + if (ap.mTitle == null || ap.mTitle.length() == 0) { + ap.mTitle = getString(R.string.title_usb_accessory); + } + ap.mMessage = getString(R.string.usb_accessory_uri_prompt, mUri); + ap.mPositiveButtonText = getString(R.string.label_view); + ap.mNegativeButtonText = getString(R.string.label_ignore); + ap.mPositiveButtonListener = this; + ap.mNegativeButtonListener = this; + + setupAlert(); + } + + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + // launch the browser + Intent intent = new Intent(Intent.ACTION_VIEW, mUri); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "startActivity failed for " + mUri); + } + } + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java new file mode 100644 index 0000000..4fa3ad0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDisconnectedReceiver.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 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.systemui.usb; + +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.UsbManager; + +// This class is used to close UsbPermissionsActivity and UsbResolverActivity +// if their accessory is disconnected while the dialog is still open +class UsbDisconnectedReceiver extends BroadcastReceiver { + private final Activity mActivity; + private UsbAccessory mAccessory; + + public UsbDisconnectedReceiver(Activity activity, UsbAccessory accessory) { + mActivity = activity; + mAccessory = accessory; + + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_DETACHED); + activity.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { + UsbAccessory accessory = + (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (accessory != null && accessory.equals(mAccessory)) { + mActivity.finish(); + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java new file mode 100644 index 0000000..c017676 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 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.systemui.usb; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.TextView; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import com.android.systemui.R; + +public class UsbPermissionActivity extends AlertActivity + implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener { + + private static final String TAG = "UsbPermissionActivity"; + + private CheckBox mAlwaysCheck; + private TextView mClearDefaultHint; + private UsbAccessory mAccessory; + private PendingIntent mPendingIntent; + private String mPackageName; + private int mUid; + private boolean mPermissionGranted; + private UsbDisconnectedReceiver mDisconnectedReceiver; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + Intent intent = getIntent(); + mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); + mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT); + mUid = intent.getIntExtra("uid", 0); + mPackageName = intent.getStringExtra("package"); + + PackageManager packageManager = getPackageManager(); + ApplicationInfo aInfo; + try { + aInfo = packageManager.getApplicationInfo(mPackageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to look up package name", e); + finish(); + return; + } + String appName = aInfo.loadLabel(packageManager).toString(); + + final AlertController.AlertParams ap = mAlertParams; + ap.mIcon = aInfo.loadIcon(packageManager); + ap.mTitle = appName; + ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName); + ap.mPositiveButtonText = getString(com.android.internal.R.string.ok); + ap.mNegativeButtonText = getString(com.android.internal.R.string.cancel); + ap.mPositiveButtonListener = this; + ap.mNegativeButtonListener = this; + + // add "always use" checkbox + LayoutInflater inflater = (LayoutInflater)getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); + mAlwaysCheck = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse); + mAlwaysCheck.setText(com.android.internal.R.string.alwaysUse); + mAlwaysCheck.setOnCheckedChangeListener(this); + mClearDefaultHint = (TextView)ap.mView.findViewById( + com.android.internal.R.id.clearDefaultHint); + mClearDefaultHint.setVisibility(View.GONE); + + setupAlert(); + + } + + @Override + public void onDestroy() { + IBinder b = ServiceManager.getService(USB_SERVICE); + IUsbManager service = IUsbManager.Stub.asInterface(b); + + // send response via pending intent + Intent intent = new Intent(); + try { + if (mAccessory != null) { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); + if (mPermissionGranted) { + service.grantAccessoryPermission(mAccessory, mUid); + if (mAlwaysCheck.isChecked()) { + service.setAccessoryPackage(mAccessory, mPackageName); + } + } + } + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted); + mPendingIntent.send(this, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "PendingIntent was cancelled"); + } catch (RemoteException e) { + Log.e(TAG, "IUsbService connection failed", e); + } + + if (mDisconnectedReceiver != null) { + unregisterReceiver(mDisconnectedReceiver); + } + super.onDestroy(); + } + + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + mPermissionGranted = true; + } + finish(); + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mClearDefaultHint == null) return; + + if(isChecked) { + mClearDefaultHint.setVisibility(View.VISIBLE); + } else { + mClearDefaultHint.setVisibility(View.GONE); + } + } +} diff --git a/services/java/com/android/server/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index 02669fd..2de56e5 100644 --- a/services/java/com/android/server/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.usb; +package com.android.systemui.usb; import com.android.internal.app.ResolverActivity; @@ -38,6 +38,9 @@ public class UsbResolverActivity extends ResolverActivity { public static final String TAG = "UsbResolverActivity"; public static final String EXTRA_RESOLVE_INFOS = "rlist"; + private UsbAccessory mAccessory; + private UsbDisconnectedReceiver mDisconnectedReceiver; + @Override protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); @@ -49,7 +52,6 @@ public class UsbResolverActivity extends ResolverActivity { } Intent target = (Intent)targetParcelable; ArrayList<ResolveInfo> rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS); - Log.d(TAG, "rList.size() " + rList.size()); CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity); super.onCreate(savedInstanceState, target, title, null, rList, true, /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ @@ -57,6 +59,22 @@ public class UsbResolverActivity extends ResolverActivity { This is necessary because this activity is needed for the user to allow the application permission to access the device */ ); + + mAccessory = (UsbAccessory)target.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); + if (mAccessory == null) { + Log.e(TAG, "accessory is null"); + finish(); + return; + } + mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory); + } + + @Override + protected void onDestroy() { + if (mDisconnectedReceiver != null) { + unregisterReceiver(mDisconnectedReceiver); + } + super.onDestroy(); } protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { @@ -64,19 +82,14 @@ public class UsbResolverActivity extends ResolverActivity { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); int uid = ri.activityInfo.applicationInfo.uid; - String action = intent.getAction(); - if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) { - UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra( - UsbManager.EXTRA_ACCESSORY); - // grant permission for the accessory - service.grantAccessoryPermission(accessory, uid); - // set or clear default setting - if (alwaysCheck) { - service.setAccessoryPackage(accessory, ri.activityInfo.packageName); - } else { - service.setAccessoryPackage(accessory, null); - } + // grant permission for the accessory + service.grantAccessoryPermission(mAccessory, uid); + // set or clear default setting + if (alwaysCheck) { + service.setAccessoryPackage(mAccessory, ri.activityInfo.packageName); + } else { + service.setAccessoryPackage(mAccessory, null); } try { diff --git a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java index 0cb788e..8f6de2f 100644 --- a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java +++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java @@ -16,11 +16,14 @@ package com.android.server.usb; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; @@ -31,7 +34,7 @@ import android.os.Binder; import android.os.FileUtils; import android.os.Process; import android.util.Log; -import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.Xml; import com.android.internal.content.PackageMonitor; @@ -60,10 +63,11 @@ class UsbDeviceSettingsManager { private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); private final Context mContext; + private final PackageManager mPackageManager; - // maps UID to user approved USB accessories - private final SparseArray<ArrayList<AccessoryFilter>> mAccessoryPermissionMap = - new SparseArray<ArrayList<AccessoryFilter>>(); + // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory + private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = + new HashMap<UsbAccessory, SparseBooleanArray>(); // Maps AccessoryFilter to user preferred application package private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = new HashMap<AccessoryFilter, String>(); @@ -79,22 +83,18 @@ class UsbDeviceSettingsManager { public final String mManufacturer; // USB accessory model (or null for unspecified) public final String mModel; - // USB accessory type (or null for unspecified) - public final String mType; // USB accessory version (or null for unspecified) public final String mVersion; - public AccessoryFilter(String manufacturer, String model, String type, String version) { + public AccessoryFilter(String manufacturer, String model, String version) { mManufacturer = manufacturer; mModel = model; - mType = type; mVersion = version; } public AccessoryFilter(UsbAccessory accessory) { mManufacturer = accessory.getManufacturer(); mModel = accessory.getModel(); - mType = accessory.getType(); mVersion = accessory.getVersion(); } @@ -102,7 +102,6 @@ class UsbDeviceSettingsManager { throws XmlPullParserException, IOException { String manufacturer = null; String model = null; - String type = null; String version = null; int count = parser.getAttributeCount(); @@ -114,13 +113,11 @@ class UsbDeviceSettingsManager { manufacturer = value; } else if ("model".equals(name)) { model = value; - } else if ("type".equals(name)) { - type = value; } else if ("version".equals(name)) { version = value; } } - return new AccessoryFilter(manufacturer, model, type, version); + return new AccessoryFilter(manufacturer, model, version); } public void write(XmlSerializer serializer)throws IOException { @@ -131,9 +128,6 @@ class UsbDeviceSettingsManager { if (mModel != null) { serializer.attribute(null, "model", mModel); } - if (mType != null) { - serializer.attribute(null, "type", mType); - } if (mVersion != null) { serializer.attribute(null, "version", mVersion); } @@ -143,29 +137,33 @@ class UsbDeviceSettingsManager { public boolean matches(UsbAccessory acc) { if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false; if (mModel != null && !acc.getModel().equals(mModel)) return false; - if (mType != null && !acc.getType().equals(mType)) return false; if (mVersion != null && !acc.getVersion().equals(mVersion)) return false; return true; } + public boolean matches(AccessoryFilter f) { + if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false; + if (mModel != null && !f.mModel.equals(mModel)) return false; + if (mVersion != null && !f.mVersion.equals(mVersion)) return false; + return true; + } + @Override public boolean equals(Object obj) { // can't compare if we have wildcard strings - if (mManufacturer == null || mModel == null || mType == null || mVersion == null) { + if (mManufacturer == null || mModel == null || mVersion == null) { return false; } if (obj instanceof AccessoryFilter) { AccessoryFilter filter = (AccessoryFilter)obj; return (mManufacturer.equals(filter.mManufacturer) && mModel.equals(filter.mModel) && - mType.equals(filter.mType) && mVersion.equals(filter.mVersion)); } if (obj instanceof UsbAccessory) { UsbAccessory accessory = (UsbAccessory)obj; return (mManufacturer.equals(accessory.getManufacturer()) && mModel.equals(accessory.getModel()) && - mType.equals(accessory.getType()) && mVersion.equals(accessory.getVersion())); } return false; @@ -175,7 +173,6 @@ class UsbDeviceSettingsManager { public int hashCode() { return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ (mModel == null ? 0 : mModel.hashCode()) ^ - (mType == null ? 0 : mType.hashCode()) ^ (mVersion == null ? 0 : mVersion.hashCode())); } @@ -183,59 +180,35 @@ class UsbDeviceSettingsManager { public String toString() { return "AccessoryFilter[mManufacturer=\"" + mManufacturer + "\", mModel=\"" + mModel + - "\", mType=\"" + mType + "\", mVersion=\"" + mVersion + "\"]"; } } private class MyPackageMonitor extends PackageMonitor { - public void onPackageRemoved(String packageName, int uid) { - synchronized (mLock) { - // clear all activity preferences for the package - if (clearPackageDefaultsLocked(packageName)) { - writeSettingsLocked(); - } - } + + public void onPackageAdded(String packageName, int uid) { + handlePackageUpdate(packageName); } - public void onUidRemoved(int uid) { - synchronized (mLock) { - // clear all permissions for the UID - if (clearUidDefaultsLocked(uid)) { - writeSettingsLocked(); - } - } + public void onPackageChanged(String packageName, int uid, String[] components) { + handlePackageUpdate(packageName); + } + + public void onPackageRemoved(String packageName, int uid) { + clearDefaults(packageName); } } MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); public UsbDeviceSettingsManager(Context context) { mContext = context; + mPackageManager = context.getPackageManager(); synchronized (mLock) { readSettingsLocked(); } mPackageMonitor.register(context, true); } - private void readAccessoryPermission(XmlPullParser parser) - throws XmlPullParserException, IOException { - int uid = -1; - ArrayList<AccessoryFilter> filters = new ArrayList<AccessoryFilter>(); - int count = parser.getAttributeCount(); - for (int i = 0; i < count; i++) { - if ("uid".equals(parser.getAttributeName(i))) { - uid = Integer.parseInt(parser.getAttributeValue(i)); - break; - } - } - XmlUtils.nextElement(parser); - while ("usb-accessory".equals(parser.getName())) { - filters.add(AccessoryFilter.read(parser)); - XmlUtils.nextElement(parser); - } - mAccessoryPermissionMap.put(uid, filters); - } - private void readPreference(XmlPullParser parser) throws XmlPullParserException, IOException { String packageName = null; @@ -264,9 +237,7 @@ class UsbDeviceSettingsManager { XmlUtils.nextElement(parser); while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { String tagName = parser.getName(); - if ("accessory-permission".equals(tagName)) { - readAccessoryPermission(parser); - } else if ("preference".equals(tagName)) { + if ("preference".equals(tagName)) { readPreference(parser); } else { XmlUtils.nextElement(parser); @@ -299,19 +270,6 @@ class UsbDeviceSettingsManager { serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, "settings"); - int count = mAccessoryPermissionMap.size(); - for (int i = 0; i < count; i++) { - int uid = mAccessoryPermissionMap.keyAt(i); - ArrayList<AccessoryFilter> filters = mAccessoryPermissionMap.valueAt(i); - serializer.startTag(null, "accessory-permission"); - serializer.attribute(null, "uid", Integer.toString(uid)); - int filterCount = filters.size(); - for (int j = 0; j < filterCount; j++) { - filters.get(j).write(serializer); - } - serializer.endTag(null, "accessory-permission"); - } - for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { serializer.startTag(null, "preference"); serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter)); @@ -335,11 +293,10 @@ class UsbDeviceSettingsManager { private boolean packageMatchesLocked(ResolveInfo info, String metaDataName, UsbAccessory accessory) { ActivityInfo ai = info.activityInfo; - PackageManager pm = mContext.getPackageManager(); XmlResourceParser parser = null; try { - parser = ai.loadXmlMetaData(pm, metaDataName); + parser = ai.loadXmlMetaData(mPackageManager, metaDataName); if (parser == null) { Log.w(TAG, "no meta-data for " + info); return false; @@ -367,8 +324,7 @@ class UsbDeviceSettingsManager { private final ArrayList<ResolveInfo> getAccessoryMatchesLocked( UsbAccessory accessory, Intent intent) { ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); - PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, + List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA); int count = resolveInfos.size(); for (int i = 0; i < count; i++) { @@ -381,75 +337,247 @@ class UsbDeviceSettingsManager { } public void accessoryAttached(UsbAccessory accessory) { - Intent accessoryIntent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); - accessoryIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - accessoryIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ArrayList<ResolveInfo> matches; String defaultPackage; synchronized (mLock) { - matches = getAccessoryMatchesLocked(accessory, accessoryIntent); + matches = getAccessoryMatchesLocked(accessory, intent); // Launch our default activity directly, if we have one. // Otherwise we will start the UsbResolverActivity to allow the user to choose. defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); } + resolveActivity(intent, matches, defaultPackage, accessory); + } + + public void accessoryDetached(UsbAccessory accessory) { + // clear temporary permissions for the accessory + mAccessoryPermissionMap.remove(accessory); + + Intent intent = new Intent( + UsbManager.ACTION_USB_ACCESSORY_DETACHED); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + mContext.sendBroadcast(intent); + } + + private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, + String defaultPackage, UsbAccessory accessory) { int count = matches.size(); + // don't show the resolver activity if there are no choices available - if (count == 0) return; + if (count == 0) { + if (accessory != null) { + String uri = accessory.getUri(); + if (uri != null && uri.length() > 0) { + // display URI to user + // start UsbResolverActivity so user can choose an activity + Intent dialogIntent = new Intent(); + dialogIntent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbAccessoryUriActivity"); + dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + dialogIntent.putExtra("uri", uri); + try { + mContext.startActivity(dialogIntent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "unable to start UsbAccessoryUriActivity"); + } + } + } + + // do nothing + return; + } + + ResolveInfo defaultRI = null; + if (count == 1 && defaultPackage == null) { + // Check to see if our single choice is on the system partition. + // If so, treat it as our default without calling UsbResolverActivity + ResolveInfo rInfo = matches.get(0); + if (rInfo.activityInfo != null && + rInfo.activityInfo.applicationInfo != null && + (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + defaultRI = rInfo; + } + } - if (defaultPackage != null) { + if (defaultRI == null && defaultPackage != null) { + // look for default activity for (int i = 0; i < count; i++) { ResolveInfo rInfo = matches.get(i); if (rInfo.activityInfo != null && defaultPackage.equals(rInfo.activityInfo.packageName)) { - try { - accessoryIntent.setComponent(new ComponentName( - defaultPackage, rInfo.activityInfo.name)); - mContext.startActivity(accessoryIntent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity failed", e); - } - return; + defaultRI = rInfo; + break; } } } - Intent intent = new Intent(mContext, UsbResolverActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (defaultRI != null) { + // grant permission for default activity + grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid); + + // start default activity directly + try { + intent.setComponent( + new ComponentName(defaultRI.activityInfo.packageName, + defaultRI.activityInfo.name)); + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "startActivity failed", e); + } + } else { + // start UsbResolverActivity so user can choose an activity + Intent resolverIntent = new Intent(); + resolverIntent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbResolverActivity"); + resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); + resolverIntent.putParcelableArrayListExtra("rlist", matches); + try { + mContext.startActivity(resolverIntent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "unable to start UsbResolverActivity"); + } + } + } - intent.putExtra(Intent.EXTRA_INTENT, accessoryIntent); - intent.putParcelableArrayListExtra(UsbResolverActivity.EXTRA_RESOLVE_INFOS, matches); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "unable to start UsbResolverActivity"); + private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) { + boolean changed = false; + for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) { + if (filter.matches(test)) { + mAccessoryPreferenceMap.remove(test); + changed = true; + } } + return changed; } - public void accessoryDetached(UsbAccessory accessory) { - Intent intent = new Intent( - UsbManager.ACTION_USB_ACCESSORY_DETACHED); - intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - mContext.sendBroadcast(intent); + private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo, + String metaDataName) { + XmlResourceParser parser = null; + boolean changed = false; + + try { + parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName); + if (parser == null) return false; + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("usb-accessory".equals(tagName)) { + AccessoryFilter filter = AccessoryFilter.read(parser); + if (clearCompatibleMatchesLocked(packageName, filter)) { + changed = true; + } + } + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + aInfo.toString(), e); + } finally { + if (parser != null) parser.close(); + } + return changed; } - public void checkPermission(UsbAccessory accessory) { - if (accessory == null) return; + // Check to see if the package supports any USB devices or accessories. + // If so, clear any non-matching preferences for matching devices/accessories. + private void handlePackageUpdate(String packageName) { synchronized (mLock) { - ArrayList<AccessoryFilter> filterList = mAccessoryPermissionMap.get(Binder.getCallingUid()); - if (filterList != null) { - int count = filterList.size(); - for (int i = 0; i < count; i++) { - AccessoryFilter filter = filterList.get(i); - if (filter.equals(accessory)) { - // permission allowed - return; - } + PackageInfo info; + boolean changed = false; + + try { + info = mPackageManager.getPackageInfo(packageName, + PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); + return; + } + + ActivityInfo[] activities = info.activities; + if (activities == null) return; + for (int i = 0; i < activities.length; i++) { + // check for meta-data, both for devices and accessories + if (handlePackageUpdateLocked(packageName, activities[i], + UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { + changed = true; } } + + if (changed) { + writeSettingsLocked(); + } + } + } + + public boolean hasPermission(UsbAccessory accessory) { + synchronized (mLock) { + SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); + if (uidList == null) { + return false; + } + return uidList.get(Binder.getCallingUid()); } - throw new SecurityException("User has not given permission to accessory " + accessory); + } + + public void checkPermission(UsbAccessory accessory) { + if (!hasPermission(accessory)) { + throw new SecurityException("User has not given permission to accessory " + accessory); + } + } + + private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { + int uid = Binder.getCallingUid(); + + // compare uid with packageName to foil apps pretending to be someone else + try { + ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); + if (aInfo.uid != uid) { + throw new IllegalArgumentException("package " + packageName + + " does not match caller's uid " + uid); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("package " + packageName + " not found"); + } + + long identity = Binder.clearCallingIdentity(); + intent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbPermissionActivity"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intent.EXTRA_INTENT, pi); + intent.putExtra("package", packageName); + intent.putExtra("uid", uid); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "unable to start UsbPermissionActivity"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { + Intent intent = new Intent(); + + // respond immediately if permission has already been granted + if (hasPermission(accessory)) { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + requestPermissionDialog(intent, packageName, pi); } public void setAccessoryPackage(UsbAccessory accessory, String packageName) { @@ -472,49 +600,29 @@ class UsbDeviceSettingsManager { public void grantAccessoryPermission(UsbAccessory accessory, int uid) { synchronized (mLock) { - ArrayList<AccessoryFilter> filterList = mAccessoryPermissionMap.get(uid); - if (filterList == null) { - filterList = new ArrayList<AccessoryFilter>(); - mAccessoryPermissionMap.put(uid, filterList); - } else { - int count = filterList.size(); - for (int i = 0; i < count; i++) { - if (filterList.get(i).equals(accessory)) return; - } + SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); + if (uidList == null) { + uidList = new SparseBooleanArray(1); + mAccessoryPermissionMap.put(accessory, uidList); } - filterList.add(new AccessoryFilter(accessory)); - writeSettingsLocked(); + uidList.put(uid, true); } } - public boolean hasDefaults(String packageName, int uid) { + public boolean hasDefaults(String packageName) { synchronized (mLock) { - if (mAccessoryPermissionMap.get(uid) != null) return true; - if (mAccessoryPreferenceMap.values().contains(packageName)) return true; - return false; + return mAccessoryPreferenceMap.values().contains(packageName); } } - public void clearDefaults(String packageName, int uid) { + public void clearDefaults(String packageName) { synchronized (mLock) { - boolean packageCleared = clearPackageDefaultsLocked(packageName); - boolean uidCleared = clearUidDefaultsLocked(uid); - if (packageCleared || uidCleared) { + if (clearPackageDefaultsLocked(packageName)) { writeSettingsLocked(); } } } - private boolean clearUidDefaultsLocked(int uid) { - boolean cleared = false; - int index = mAccessoryPermissionMap.indexOfKey(uid); - if (index >= 0) { - mAccessoryPermissionMap.removeAt(index); - cleared = true; - } - return cleared; - } - private boolean clearPackageDefaultsLocked(String packageName) { boolean cleared = false; synchronized (mLock) { @@ -536,14 +644,14 @@ class UsbDeviceSettingsManager { public void dump(FileDescriptor fd, PrintWriter pw) { synchronized (mLock) { pw.println(" Accessory permissions:"); - int count = mAccessoryPermissionMap.size(); - for (int i = 0; i < count; i++) { - int uid = mAccessoryPermissionMap.keyAt(i); - pw.println(" " + "uid " + uid + ":"); - ArrayList<AccessoryFilter> filters = mAccessoryPermissionMap.valueAt(i); - for (AccessoryFilter filter : filters) { - pw.println(" " + filter); + for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { + pw.print(" " + accessory + ": "); + SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); + int count = uidList.size(); + for (int i = 0; i < count; i++) { + pw.print(Integer.toString(uidList.keyAt(i)) + " "); } + pw.println(""); } pw.println(" Accessory preferences:"); for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 6825f19..7b32fd1 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -16,6 +16,7 @@ package com.android.server.usb; +import android.app.PendingIntent; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -267,10 +268,7 @@ public class UsbService extends IUsbManager.Stub { /* returns the currently attached USB accessory (device mode) */ public UsbAccessory getCurrentAccessory() { - synchronized (mLock) { - mDeviceManager.checkPermission(mCurrentAccessory); - return mCurrentAccessory; - } + return mCurrentAccessory; } /* opens the currently attached USB accessory (device mode) */ @@ -294,19 +292,28 @@ public class UsbService extends IUsbManager.Stub { mDeviceManager.setAccessoryPackage(accessory, packageName); } + public boolean hasAccessoryPermission(UsbAccessory accessory) { + return mDeviceManager.hasPermission(accessory); + } + + public void requestAccessoryPermission(UsbAccessory accessory, String packageName, + PendingIntent pi) { + mDeviceManager.requestPermission(accessory, packageName, pi); + } + public void grantAccessoryPermission(UsbAccessory accessory, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.grantAccessoryPermission(accessory, uid); } - public boolean hasDefaults(String packageName, int uid) { + public boolean hasDefaults(String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - return mDeviceManager.hasDefaults(packageName, uid); + return mDeviceManager.hasDefaults(packageName); } - public void clearDefaults(String packageName, int uid) { + public void clearDefaults(String packageName) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.clearDefaults(packageName, uid); + mDeviceManager.clearDefaults(packageName); } /* diff --git a/services/jni/com_android_server_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp index 08a526e..2ce0eaa 100644 --- a/services/jni/com_android_server_UsbService.cpp +++ b/services/jni/com_android_server_UsbService.cpp @@ -63,7 +63,6 @@ static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strA buffer[0] = 0; int length = ioctl(fd, cmd, buffer); if (buffer[0]) { - LOGD("string %d: %s", index, buffer); jstring obj = env->NewStringUTF(buffer); env->SetObjectArrayElement(strArray, index, obj); env->DeleteLocalRef(obj); @@ -73,19 +72,19 @@ static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strA static jobjectArray android_server_UsbService_getAccessoryStrings(JNIEnv *env, jobject thiz) { - LOGD("getAccessoryStrings"); int fd = open(DRIVER_NAME, O_RDWR); if (fd < 0) { LOGE("could not open %s", DRIVER_NAME); return NULL; } jclass stringClass = env->FindClass("java/lang/String"); - jobjectArray strArray = env->NewObjectArray(4, stringClass, NULL); + jobjectArray strArray = env->NewObjectArray(5, stringClass, NULL); if (!strArray) goto out; set_accessory_string(env, fd, ACCESSORY_GET_STRING_MANUFACTURER, strArray, 0); set_accessory_string(env, fd, ACCESSORY_GET_STRING_MODEL, strArray, 1); - set_accessory_string(env, fd, ACCESSORY_GET_STRING_TYPE, strArray, 2); + set_accessory_string(env, fd, ACCESSORY_GET_STRING_DESCRIPTION, strArray, 2); set_accessory_string(env, fd, ACCESSORY_GET_STRING_VERSION, strArray, 3); + set_accessory_string(env, fd, ACCESSORY_GET_STRING_URI, strArray, 4); out: close(fd); |