diff options
author | Mike Lockwood <lockwood@android.com> | 2011-02-28 17:04:23 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-02-28 17:04:23 -0800 |
commit | fae640b174f0a720690bab31a31dfd2693c67c63 (patch) | |
tree | e558c98eea382ab59eb1355c6da58456cf530e4e /services | |
parent | a8049111ffdef6b58c32121d4d9d306fa7df3897 (diff) | |
parent | 02eb8746de2d60563ec2751a34d20923192e4293 (diff) | |
download | frameworks_base-fae640b174f0a720690bab31a31dfd2693c67c63.zip frameworks_base-fae640b174f0a720690bab31a31dfd2693c67c63.tar.gz frameworks_base-fae640b174f0a720690bab31a31dfd2693c67c63.tar.bz2 |
Merge "UsbManager: Enhancements for managing USB devices and accessories"
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 1 | ||||
-rw-r--r-- | services/java/com/android/server/usb/UsbDeviceSettingsManager.java | 861 | ||||
-rw-r--r-- | services/java/com/android/server/usb/UsbResolverActivity.java | 102 | ||||
-rw-r--r-- | services/java/com/android/server/usb/UsbService.java (renamed from services/java/com/android/server/UsbService.java) | 160 | ||||
-rw-r--r-- | services/jni/com_android_server_UsbService.cpp | 7 |
5 files changed, 1091 insertions, 40 deletions
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 52c47e1..d160963 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,6 +17,7 @@ package com.android.server; import com.android.server.am.ActivityManagerService; +import com.android.server.usb.UsbService; import com.android.server.wm.WindowManagerService; import com.android.internal.app.ShutdownThread; import com.android.internal.os.BinderInternal; diff --git a/services/java/com/android/server/usb/UsbDeviceSettingsManager.java b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java new file mode 100644 index 0000000..21eb7dc --- /dev/null +++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java @@ -0,0 +1,861 @@ +/* + * 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.server.usb; + +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.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.XmlResourceParser; +import android.hardware.UsbAccessory; +import android.hardware.UsbDevice; +import android.hardware.UsbInterface; +import android.hardware.UsbManager; +import android.os.Binder; +import android.os.FileUtils; +import android.os.Process; +import android.util.Log; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +class UsbDeviceSettingsManager { + + private static final String TAG = "UsbDeviceSettingsManager"; + private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); + + private final Context mContext; + + // maps UID to user approved USB devices + final SparseArray<ArrayList<DeviceFilter>> mDevicePermissionMap = + new SparseArray<ArrayList<DeviceFilter>>(); + // maps UID to user approved USB accessories + final SparseArray<ArrayList<AccessoryFilter>> mAccessoryPermissionMap = + new SparseArray<ArrayList<AccessoryFilter>>(); + // Maps DeviceFilter to user preferred application package + final HashMap<DeviceFilter, String> mDevicePreferenceMap = + new HashMap<DeviceFilter, String>(); + // Maps DeviceFilter to user preferred application package + final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = + new HashMap<AccessoryFilter, String>(); + + // This class is used to describe a USB device. + // When used in HashMaps all values must be specified, + // but wildcards can be used for any of the fields in + // the package meta-data. + private static class DeviceFilter { + // USB Vendor ID (or -1 for unspecified) + public final int mVendorId; + // USB Product ID (or -1 for unspecified) + public final int mProductId; + // USB device or interface class (or -1 for unspecified) + public final int mClass; + // USB device subclass (or -1 for unspecified) + public final int mSubclass; + // USB device protocol (or -1 for unspecified) + public final int mProtocol; + + public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol) { + mVendorId = vid; + mProductId = pid; + mClass = clasz; + mSubclass = subclass; + mProtocol = protocol; + } + + public DeviceFilter(UsbDevice device) { + mVendorId = device.getVendorId(); + mProductId = device.getProductId(); + mClass = device.getDeviceClass(); + mSubclass = device.getDeviceSubclass(); + mProtocol = device.getDeviceProtocol(); + } + + public static DeviceFilter read(XmlPullParser parser) + throws XmlPullParserException, IOException { + int vendorId = -1; + int productId = -1; + int deviceClass = -1; + int deviceSubclass = -1; + int deviceProtocol = -1; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + // All attribute values are ints + int value = Integer.parseInt(parser.getAttributeValue(i)); + + if ("vendor-id".equals(name)) { + vendorId = value; + } else if ("product-id".equals(name)) { + productId = value; + } else if ("class".equals(name)) { + deviceClass = value; + } else if ("subclass".equals(name)) { + deviceSubclass = value; + } else if ("protocol".equals(name)) { + deviceProtocol = value; + } + } + return new DeviceFilter(vendorId, productId, + deviceClass, deviceSubclass, deviceProtocol); + } + + public void write(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "usb-device"); + if (mVendorId != -1) { + serializer.attribute(null, "vendor-id", Integer.toString(mVendorId)); + } + if (mProductId != -1) { + serializer.attribute(null, "product-id", Integer.toString(mProductId)); + } + if (mClass != -1) { + serializer.attribute(null, "class", Integer.toString(mClass)); + } + if (mSubclass != -1) { + serializer.attribute(null, "subclass", Integer.toString(mSubclass)); + } + if (mProtocol != -1) { + serializer.attribute(null, "protocol", Integer.toString(mProtocol)); + } + serializer.endTag(null, "usb-device"); + } + + private boolean matches(int clasz, int subclass, int protocol) { + return ((mClass == -1 || clasz == mClass) && + (mSubclass == -1 || subclass == mSubclass) && + (mProtocol == -1 || protocol == mProtocol)); + } + + public boolean matches(UsbDevice device) { + if (mVendorId != -1 && device.getVendorId() != mVendorId) return false; + if (mProductId != -1 && device.getProductId() != mProductId) return false; + + // check device class/subclass/protocol + if (matches(device.getDeviceClass(), device.getDeviceSubclass(), + device.getDeviceProtocol())) return true; + + // if device doesn't match, check the interfaces + int count = device.getInterfaceCount(); + for (int i = 0; i < count; i++) { + UsbInterface intf = device.getInterface(i); + if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), + intf.getInterfaceProtocol())) return true; + } + + return false; + } + + @Override + public boolean equals(Object obj) { + // can't compare if we have wildcard strings + if (mVendorId == -1 || mProductId == -1 || + mClass == -1 || mSubclass == -1 || mProtocol == -1) { + return false; + } + if (obj instanceof DeviceFilter) { + DeviceFilter filter = (DeviceFilter)obj; + return (filter.mVendorId == mVendorId && + filter.mProductId == mProductId && + filter.mClass == mClass && + filter.mSubclass == mSubclass && + filter.mProtocol == mProtocol); + } + if (obj instanceof UsbDevice) { + UsbDevice device = (UsbDevice)obj; + return (device.getVendorId() == mVendorId && + device.getProductId() == mProductId && + device.getDeviceClass() == mClass && + device.getDeviceSubclass() == mSubclass && + device.getDeviceProtocol() == mProtocol); + } + return false; + } + + @Override + public int hashCode() { + return (((mVendorId << 16) | mProductId) ^ + ((mClass << 16) | (mSubclass << 8) | mProtocol)); + } + + @Override + public String toString() { + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + "]"; + } + } + + // This class is used to describe a USB accessory. + // When used in HashMaps all values must be specified, + // but wildcards can be used for any of the fields in + // the package meta-data. + private static class AccessoryFilter { + // USB accessory manufacturer (or null for unspecified) + 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) { + mManufacturer = manufacturer; + mModel = model; + mType = type; + mVersion = version; + } + + public AccessoryFilter(UsbAccessory accessory) { + mManufacturer = accessory.getManufacturer(); + mModel = accessory.getModel(); + mType = accessory.getType(); + mVersion = accessory.getVersion(); + } + + public static AccessoryFilter read(XmlPullParser parser) + throws XmlPullParserException, IOException { + String manufacturer = null; + String model = null; + String type = null; + String version = null; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + + if ("manufacturer".equals(name)) { + 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); + } + + public void write(XmlSerializer serializer)throws IOException { + serializer.startTag(null, "usb-accessory"); + if (mManufacturer != null) { + serializer.attribute(null, "manufacturer", mManufacturer); + } + if (mModel != null) { + serializer.attribute(null, "model", mModel); + } + if (mType != null) { + serializer.attribute(null, "type", mType); + } + if (mVersion != null) { + serializer.attribute(null, "version", mVersion); + } + serializer.endTag(null, "usb-accessory"); + } + + 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; + } + + @Override + public boolean equals(Object obj) { + // can't compare if we have wildcard strings + if (mManufacturer == null || mModel == null || mType == 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; + } + + @Override + public int hashCode() { + return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^ + (mModel == null ? 0 : mModel.hashCode()) ^ + (mType == null ? 0 : mType.hashCode()) ^ + (mVersion == null ? 0 : mVersion.hashCode())); + } + + @Override + 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) { + // clear all activity preferences for the package + if (clearPackageDefaults(packageName)) { + writeSettings(); + } + } + + public void onUidRemoved(int uid) { + // clear all permissions for the UID + if (clearUidDefaults(uid)) { + writeSettings(); + } + } + } + MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); + + public UsbDeviceSettingsManager(Context context) { + mContext = context; + readSettings(); + mPackageMonitor.register(context, true); + } + + private void readDevicePermission(XmlPullParser parser) + throws XmlPullParserException, IOException { + int uid = -1; + ArrayList<DeviceFilter> filters = new ArrayList<DeviceFilter>(); + 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-device".equals(parser.getName())) { + filters.add(DeviceFilter.read(parser)); + XmlUtils.nextElement(parser); + } + mDevicePermissionMap.put(uid, filters); + } + + 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; + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + if ("package".equals(parser.getAttributeName(i))) { + packageName = parser.getAttributeValue(i); + break; + } + } + XmlUtils.nextElement(parser); + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + mDevicePreferenceMap.put(filter, packageName); + } else if ("usb-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + mAccessoryPreferenceMap.put(filter, packageName); + } + XmlUtils.nextElement(parser); + } + + private void readSettings() { + FileInputStream stream = null; + try { + stream = new FileInputStream(sSettingsFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("device-permission".equals(tagName)) { + readDevicePermission(parser); + } else if ("accessory-permission".equals(tagName)) { + readAccessoryPermission(parser); + } else if ("preference".equals(tagName)) { + readPreference(parser); + } else { + XmlUtils.nextElement(parser); + } + } + } catch (FileNotFoundException e) { + Log.w(TAG, "settings file not found"); + } catch (Exception e) { + Log.e(TAG, "error reading settings file, deleting to start fresh", e); + sSettingsFile.delete(); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + } + + private void writeSettings() { + FileOutputStream fos = null; + try { + FileOutputStream fstr = new FileOutputStream(sSettingsFile); + Log.d(TAG, "writing settings to " + fstr); + BufferedOutputStream str = new BufferedOutputStream(fstr); + FastXmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startTag(null, "settings"); + + int count = mDevicePermissionMap.size(); + for (int i = 0; i < count; i++) { + int uid = mDevicePermissionMap.keyAt(i); + ArrayList<DeviceFilter> filters = mDevicePermissionMap.valueAt(i); + serializer.startTag(null, "device-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, "device-permission"); + } + + 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 (DeviceFilter filter : mDevicePreferenceMap.keySet()) { + serializer.startTag(null, "preference"); + serializer.attribute(null, "package", mDevicePreferenceMap.get(filter)); + filter.write(serializer); + serializer.endTag(null, "preference"); + } + + for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { + serializer.startTag(null, "preference"); + serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter)); + filter.write(serializer); + serializer.endTag(null, "preference"); + } + + serializer.endTag(null, "settings"); + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + } catch (Exception e) { + Log.e(TAG, "error writing settings file, deleting to start fresh", e); + sSettingsFile.delete(); + } + } + + // Checks to see if a package matches a device or accessory. + // Only one of device and accessory should be non-null. + private boolean packageMatches(ResolveInfo info, String metaDataName, + UsbDevice device, UsbAccessory accessory) { + ActivityInfo ai = info.activityInfo; + PackageManager pm = mContext.getPackageManager(); + + XmlResourceParser parser = null; + try { + parser = ai.loadXmlMetaData(pm, metaDataName); + if (parser == null) { + Log.w(TAG, "no meta-data for " + info); + return false; + } + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if (device != null && "usb-device".equals(tagName)) { + DeviceFilter filter = DeviceFilter.read(parser); + if (filter.matches(device)) { + return true; + } + } + else if (accessory != null && "usb-accessory".equals(tagName)) { + AccessoryFilter filter = AccessoryFilter.read(parser); + if (filter.matches(accessory)) { + return true; + } + } + XmlUtils.nextElement(parser); + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + info.toString(), e); + } finally { + if (parser != null) parser.close(); + } + return false; + } + + private final ArrayList<ResolveInfo> getDeviceMatches(UsbDevice device, Intent intent) { + ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, + PackageManager.GET_META_DATA); + int count = resolveInfos.size(); + for (int i = 0; i < count; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + if (packageMatches(resolveInfo, intent.getAction(), device, null)) { + matches.add(resolveInfo); + } + } + return matches; + } + + private final ArrayList<ResolveInfo> getAccessoryMatches(UsbAccessory accessory, Intent intent) { + ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, + PackageManager.GET_META_DATA); + int count = resolveInfos.size(); + for (int i = 0; i < count; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + if (packageMatches(resolveInfo, intent.getAction(), null, accessory)) { + matches.add(resolveInfo); + } + } + return matches; + } + + public void deviceAttached(UsbDevice device) { + Intent deviceIntent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); + deviceIntent.putExtra(UsbManager.EXTRA_DEVICE, device); + deviceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + ArrayList<ResolveInfo> matches = getDeviceMatches(device, deviceIntent); + // Launch our default activity directly, if we have one. + // Otherwise we will start the UsbResolverActivity to allow the user to choose. + String defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device)); + if (defaultPackage != null) { + int count = matches.size(); + for (int i = 0; i < count; i++) { + ResolveInfo rInfo = matches.get(i); + if (rInfo.activityInfo != null && + defaultPackage.equals(rInfo.activityInfo.packageName)) { + try { + deviceIntent.setComponent(new ComponentName( + defaultPackage, rInfo.activityInfo.name)); + mContext.startActivity(deviceIntent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "startActivity failed", e); + } + return; + } + } + } + + Intent intent = new Intent(mContext, UsbResolverActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + intent.putExtra(Intent.EXTRA_INTENT, deviceIntent); + intent.putParcelableArrayListExtra(UsbResolverActivity.EXTRA_RESOLVE_INFOS, + matches); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "unable to start UsbResolverActivity"); + } + } + + public void deviceDetached(UsbDevice device) { + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + Log.d(TAG, "usbDeviceRemoved, sending " + intent); + mContext.sendBroadcast(intent); + } + + 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); + + ArrayList<ResolveInfo> matches = getAccessoryMatches(accessory, accessoryIntent); + // Launch our default activity directly, if we have one. + // Otherwise we will start the UsbResolverActivity to allow the user to choose. + String defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)); + if (defaultPackage != null) { + int count = matches.size(); + 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; + } + } + } + + Intent intent = new Intent(mContext, UsbResolverActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + 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"); + } + } + + public void accessoryDetached(UsbAccessory accessory) { + Intent intent = new Intent( + UsbManager.ACTION_USB_ACCESSORY_DETACHED); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + mContext.sendBroadcast(intent); + } + + public void checkPermission(UsbDevice device) { + if (device == null) return; + ArrayList<DeviceFilter> filterList = mDevicePermissionMap.get(Binder.getCallingUid()); + if (filterList != null) { + int count = filterList.size(); + for (int i = 0; i < count; i++) { + DeviceFilter filter = filterList.get(i); + if (filter.equals(device)) { + // permission allowed + return; + } + } + } + throw new SecurityException("User has not given permission to device " + device); + } + + public void checkPermission(UsbAccessory accessory) { + if (accessory == null) return; + 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; + } + } + } + throw new SecurityException("User has not given permission to accessory " + accessory); + } + + public void setDevicePackage(UsbDevice device, String packageName) { + DeviceFilter filter = new DeviceFilter(device); + if (packageName == null) { + mDevicePreferenceMap.remove(filter); + } else { + mDevicePreferenceMap.put(filter, packageName); + } + // FIXME - only if changed + writeSettings(); + } + + public void setAccessoryPackage(UsbAccessory accessory, String packageName) { + AccessoryFilter filter = new AccessoryFilter(accessory); + if (packageName == null) { + mAccessoryPreferenceMap.remove(filter); + } else { + mAccessoryPreferenceMap.put(filter, packageName); + } + // FIXME - only if changed + writeSettings(); + } + + public void grantDevicePermission(UsbDevice device, int uid) { + ArrayList<DeviceFilter> filterList = mDevicePermissionMap.get(uid); + if (filterList == null) { + filterList = new ArrayList<DeviceFilter>(); + mDevicePermissionMap.put(uid, filterList); + } else { + int count = filterList.size(); + for (int i = 0; i < count; i++) { + if (filterList.get(i).equals(device)) return; + } + } + filterList.add(new DeviceFilter(device)); + writeSettings(); + } + + public void grantAccessoryPermission(UsbAccessory accessory, int uid) { + 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; + } + } + filterList.add(new AccessoryFilter(accessory)); + writeSettings(); + } + + public boolean hasDefaults(String packageName, int uid) { + if (mDevicePermissionMap.get(uid) != null) return true; + if (mAccessoryPermissionMap.get(uid) != null) return true; + if (mDevicePreferenceMap.values().contains(packageName)) return true; + if (mAccessoryPreferenceMap.values().contains(packageName)) return true; + return false; + } + + public void clearDefaults(String packageName, int uid) { + boolean packageCleared = clearPackageDefaults(packageName); + boolean uidCleared = clearUidDefaults(uid); + if (packageCleared || uidCleared) { + writeSettings(); + } + } + + private boolean clearUidDefaults(int uid) { + boolean cleared = false; + int index = mDevicePermissionMap.indexOfKey(uid); + if (index >= 0) { + mDevicePermissionMap.removeAt(index); + cleared = true; + } + index = mAccessoryPermissionMap.indexOfKey(uid); + if (index >= 0) { + mAccessoryPermissionMap.removeAt(index); + cleared = true; + } + return cleared; + } + + private boolean clearPackageDefaults(String packageName) { + boolean cleared = false; + if (mDevicePreferenceMap.containsValue(packageName)) { + // make a copy of the key set to avoid ConcurrentModificationException + Object[] keys = mDevicePreferenceMap.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + Object key = keys[i]; + if (packageName.equals(mDevicePreferenceMap.get(key))) { + mDevicePreferenceMap.remove(key); + cleared = true; + } + } + } + if (mAccessoryPreferenceMap.containsValue(packageName)) { + // make a copy of the key set to avoid ConcurrentModificationException + Object[] keys = mAccessoryPreferenceMap.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + Object key = keys[i]; + if (packageName.equals(mAccessoryPreferenceMap.get(key))) { + mAccessoryPreferenceMap.remove(key); + cleared = true; + } + } + } + return cleared; + } + + public void dump(FileDescriptor fd, PrintWriter pw) { + pw.println(" Device permissions:"); + int count = mDevicePermissionMap.size(); + for (int i = 0; i < count; i++) { + int uid = mDevicePermissionMap.keyAt(i); + pw.println(" " + "uid " + uid + ":"); + ArrayList<DeviceFilter> filters = mDevicePermissionMap.valueAt(i); + for (DeviceFilter filter : filters) { + pw.println(" " + filter); + } + } + pw.println(" Accessory permissions:"); + 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); + } + } + pw.println(" Device preferences:"); + for (DeviceFilter filter : mDevicePreferenceMap.keySet()) { + pw.println(" " + filter + ": " + mDevicePreferenceMap.get(filter)); + } + pw.println(" Accessory preferences:"); + for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) { + pw.println(" " + filter + ": " + mAccessoryPreferenceMap.get(filter)); + } + } +} diff --git a/services/java/com/android/server/usb/UsbResolverActivity.java b/services/java/com/android/server/usb/UsbResolverActivity.java new file mode 100644 index 0000000..1bb3c21 --- /dev/null +++ b/services/java/com/android/server/usb/UsbResolverActivity.java @@ -0,0 +1,102 @@ +/* + * 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.server.usb; + +import com.android.internal.app.ResolverActivity; + +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.hardware.IUsbManager; +import android.hardware.UsbAccessory; +import android.hardware.UsbDevice; +import android.hardware.UsbManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.util.ArrayList; + +/* Activity for choosing an application for a USB device or accessory */ +public class UsbResolverActivity extends ResolverActivity { + public static final String TAG = "UsbResolverActivity"; + public static final String EXTRA_RESOLVE_INFOS = "rlist"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Intent intent = getIntent(); + Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); + if (!(targetParcelable instanceof Intent)) { + Log.w("UsbResolverActivity", "Target is not an intent: " + targetParcelable); + finish(); + return; + } + 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. */ + true /* Set alwaysChoose to display activity when only one choice is available. + This is necessary because this activity is needed for the user to allow + the application permission to access the device */ + ); + } + + protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { + try { + 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_DEVICE_ATTACHED.equals(action)) { + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + // grant permission for the device + service.grantDevicePermission(device, uid); + // set or clear default setting + if (alwaysCheck) { + service.setDevicePackage(device, ri.activityInfo.packageName); + } else { + service.setDevicePackage(device, null); + } + } else 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); + } + } + + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "startActivity failed", e); + } + } catch (RemoteException e) { + Log.e(TAG, "onIntentSelected failed", e); + } + } +} diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 2f84713..de4ac03 100644 --- a/services/java/com/android/server/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.usb; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.hardware.IUsbManager; import android.hardware.UsbAccessory; import android.hardware.UsbConstants; @@ -27,6 +28,7 @@ import android.hardware.UsbEndpoint; import android.hardware.UsbInterface; import android.hardware.UsbManager; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -38,10 +40,13 @@ import android.util.Log; import android.util.Slog; import java.io.File; +import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileReader; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * UsbService monitors for changes to USB state. @@ -50,7 +55,7 @@ import java.util.HashMap; * Accessory mode is a special case of USB device mode, where the android device is * connected to a USB host that supports the android accessory protocol. */ -class UsbService extends IUsbManager.Stub { +public class UsbService extends IUsbManager.Stub { private static final String TAG = UsbService.class.getSimpleName(); private static final boolean LOG = false; @@ -102,6 +107,7 @@ class UsbService extends IUsbManager.Stub { private final Context mContext; private final Object mLock = new Object(); + private final UsbDeviceSettingsManager mDeviceManager; /* * Handles USB function enable/disable events (device mode) @@ -139,11 +145,9 @@ class UsbService extends IUsbManager.Stub { if (enteringAccessoryMode) { String[] strings = nativeGetAccessoryStrings(); if (strings != null) { - Log.d(TAG, "entering USB accessory mode"); mCurrentAccessory = new UsbAccessory(strings); - Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); - intent.putExtra(UsbManager.EXTRA_ACCESSORY, mCurrentAccessory); - mContext.sendBroadcast(intent); + Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); + mDeviceManager.accessoryAttached(mCurrentAccessory); } else { Log.e(TAG, "nativeGetAccessoryStrings failed"); } @@ -170,7 +174,7 @@ class UsbService extends IUsbManager.Stub { mConnected = intState; // trigger an Intent broadcast if (mSystemReady) { - // debounce disconnects + // debounce disconnects to avoid problems bringing up USB tethering update(mConnected == 0); } } else if ("usb_configuration".equals(name)) { @@ -202,6 +206,8 @@ class UsbService extends IUsbManager.Stub { public UsbService(Context context) { mContext = context; + mDeviceManager = new UsbDeviceSettingsManager(context); + mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); @@ -345,11 +351,7 @@ class UsbService extends IUsbManager.Stub { UsbDevice device = new UsbDevice(deviceName, vendorID, productID, deviceClass, deviceSubclass, deviceProtocol, interfaces); mDevices.put(deviceName, device); - - Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED); - intent.putExtra(UsbManager.EXTRA_DEVICE, device); - Log.d(TAG, "usbDeviceAdded, sending " + intent); - mContext.sendBroadcast(intent); + mDeviceManager.deviceAttached(device); } } @@ -358,10 +360,7 @@ class UsbService extends IUsbManager.Stub { synchronized (mLock) { UsbDevice device = mDevices.remove(deviceName); if (device != null) { - Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); - intent.putExtra(UsbManager.EXTRA_DEVICE, device); - Log.d(TAG, "usbDeviceRemoved, sending " + intent); - mContext.sendBroadcast(intent); + mDeviceManager.deviceDetached(device); } } } @@ -377,7 +376,7 @@ class UsbService extends IUsbManager.Stub { new Thread(null, runnable, "UsbService host thread").start(); } - void systemReady() { + public void systemReady() { synchronized (mLock) { if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_hasUsbHostSupport)) { @@ -402,7 +401,6 @@ class UsbService extends IUsbManager.Stub { /* Returns a list of all currently attached USB devices (host mdoe) */ public void getDeviceList(Bundle devices) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null); synchronized (mLock) { for (String name : mDevices.keySet()) { devices.putParcelable(name, mDevices.get(name)); @@ -412,28 +410,85 @@ class UsbService extends IUsbManager.Stub { /* Opens the specified USB device (host mode) */ public ParcelFileDescriptor openDevice(String deviceName) { - if (isBlackListed(deviceName)) { - throw new SecurityException("USB device is on a restricted bus"); - } - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null); - if (mDevices.get(deviceName) == null) { - // if it is not in mDevices, it either does not exist or is blacklisted - throw new IllegalArgumentException( - "device " + deviceName + " does not exist or is restricted"); + synchronized (mLock) { + if (isBlackListed(deviceName)) { + throw new SecurityException("USB device is on a restricted bus"); + } + UsbDevice device = mDevices.get(deviceName); + if (device == null) { + // if it is not in mDevices, it either does not exist or is blacklisted + throw new IllegalArgumentException( + "device " + deviceName + " does not exist or is restricted"); + } + mDeviceManager.checkPermission(device); + return nativeOpenDevice(deviceName); } - return nativeOpenDevice(deviceName); } /* returns the currently attached USB accessory (device mode) */ public UsbAccessory getCurrentAccessory() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null); - return mCurrentAccessory; + synchronized (mLock) { + mDeviceManager.checkPermission(mCurrentAccessory); + return mCurrentAccessory; + } } /* opens the currently attached USB accessory (device mode) */ - public ParcelFileDescriptor openAccessory() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_USB, null); - return nativeOpenAccessory(); + public ParcelFileDescriptor openAccessory(UsbAccessory accessory) { + synchronized (mLock) { + if (mCurrentAccessory == null) { + throw new IllegalArgumentException("no accessory attached"); + } + if (!mCurrentAccessory.equals(accessory)) { + Log.e(TAG, accessory.toString() + " does not match current accessory " + + mCurrentAccessory); + throw new IllegalArgumentException("accessory not attached"); + } + mDeviceManager.checkPermission(mCurrentAccessory); + return nativeOpenAccessory(); + } + } + + public void setDevicePackage(UsbDevice device, String packageName) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.setDevicePackage(device, packageName); + } + } + + public void setAccessoryPackage(UsbAccessory accessory, String packageName) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.setAccessoryPackage(accessory, packageName); + } + } + + public void grantDevicePermission(UsbDevice device, int uid) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.grantDevicePermission(device, uid); + } + } + + public void grantAccessoryPermission(UsbAccessory accessory, int uid) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.grantAccessoryPermission(accessory, uid); + } + } + + public boolean hasDefaults(String packageName, int uid) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + return mDeviceManager.hasDefaults(packageName, uid); + } + } + + public void clearDefaults(String packageName, int uid) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.clearDefaults(packageName, uid); + } } /* @@ -470,10 +525,7 @@ class UsbService extends IUsbManager.Stub { } mAccessoryRestoreFunctions.clear(); - Intent intent = new Intent( - UsbManager.ACTION_USB_ACCESSORY_DETACHED); - intent.putExtra(UsbManager.EXTRA_ACCESSORY, mCurrentAccessory); - mContext.sendBroadcast(intent); + mDeviceManager.accessoryDetached(mCurrentAccessory); mCurrentAccessory = null; // this will cause an immediate reset of the USB bus, @@ -513,6 +565,42 @@ class UsbService extends IUsbManager.Stub { } }; + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump UsbManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mLock) { + pw.println("USB Manager State:"); + + pw.println(" USB Device State:"); + pw.print(" Enabled Functions: "); + for (int i = 0; i < mEnabledFunctions.size(); i++) { + pw.print(mEnabledFunctions.get(i) + " "); + } + pw.println(""); + pw.print(" Disabled Functions: "); + for (int i = 0; i < mDisabledFunctions.size(); i++) { + pw.print(mDisabledFunctions.get(i) + " "); + } + pw.println(""); + pw.println(" mConnected: " + mConnected + ", mConfiguration: " + mConfiguration); + + pw.println(" USB Host State:"); + for (String name : mDevices.keySet()) { + pw.println(" " + name + ": " + mDevices.get(name)); + } + pw.println(" mCurrentAccessory: " + mCurrentAccessory); + + mDeviceManager.dump(fd, pw); + } + } + // host support private native void monitorUsbHostBus(); private native ParcelFileDescriptor nativeOpenDevice(String deviceName); diff --git a/services/jni/com_android_server_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp index 192daaf..3c49e54 100644 --- a/services/jni/com_android_server_UsbService.cpp +++ b/services/jni/com_android_server_UsbService.cpp @@ -177,7 +177,6 @@ static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strA buffer[0] = 0; int length = ioctl(fd, cmd, buffer); - LOGD("ioctl returned %d", length); if (buffer[0]) { jstring obj = env->NewStringUTF(buffer); env->SetObjectArrayElement(strArray, index, obj); @@ -236,9 +235,9 @@ static JNINativeMethod method_table[] = { int register_android_server_UsbService(JNIEnv *env) { - jclass clazz = env->FindClass("com/android/server/UsbService"); + jclass clazz = env->FindClass("com/android/server/usb/UsbService"); if (clazz == NULL) { - LOGE("Can't find com/android/server/UsbService"); + LOGE("Can't find com/android/server/usb/UsbService"); return -1; } method_usbDeviceAdded = env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;IIIII[I[I)V"); @@ -267,7 +266,7 @@ int register_android_server_UsbService(JNIEnv *env) LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL, "Unable to find constructor for android.os.ParcelFileDescriptor"); - return jniRegisterNativeMethods(env, "com/android/server/UsbService", + return jniRegisterNativeMethods(env, "com/android/server/usb/UsbService", method_table, NELEM(method_table)); } |