diff options
Diffstat (limited to 'services/java/com/android/server')
7 files changed, 1076 insertions, 244 deletions
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index a5333da..b26dac7 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -38,7 +38,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; -import android.hardware.UsbManager; +import android.hardware.usb.UsbManager; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 25175e2..0f03f75 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.internal.app.ShutdownThread; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; @@ -397,9 +398,10 @@ class ServerThread extends Thread { } try { - Slog.i(TAG, "USB Observer"); + Slog.i(TAG, "USB Service"); // Listen for USB changes usb = new UsbService(context); + ServiceManager.addService(Context.USB_SERVICE, usb); } catch (Throwable e) { Slog.e(TAG, "Failure starting UsbService", e); } diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java deleted file mode 100644 index 578db0e..0000000 --- a/services/java/com/android/server/UsbService.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.server; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.hardware.UsbManager; -import android.net.Uri; -import android.os.Handler; -import android.os.Message; -import android.os.UEventObserver; -import android.provider.Settings; -import android.util.Log; -import android.util.Slog; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.util.ArrayList; - -/** - * <p>UsbService monitors for changes to USB state. - */ -class UsbService { - private static final String TAG = UsbService.class.getSimpleName(); - private static final boolean LOG = false; - - private static final String USB_CONNECTED_MATCH = - "DEVPATH=/devices/virtual/switch/usb_connected"; - private static final String USB_CONFIGURATION_MATCH = - "DEVPATH=/devices/virtual/switch/usb_configuration"; - private static final String USB_FUNCTIONS_MATCH = - "DEVPATH=/devices/virtual/usb_composite/"; - private static final String USB_CONNECTED_PATH = - "/sys/class/switch/usb_connected/state"; - private static final String USB_CONFIGURATION_PATH = - "/sys/class/switch/usb_configuration/state"; - private static final String USB_COMPOSITE_CLASS_PATH = - "/sys/class/usb_composite"; - - private static final int MSG_UPDATE = 0; - - // Delay for debouncing USB disconnects. - // We often get rapid connect/disconnect events when enabling USB functions, - // which need debouncing. - private static final int UPDATE_DELAY = 1000; - - // current connected and configuration state - private int mConnected; - private int mConfiguration; - - // last broadcasted connected and configuration state - private int mLastConnected = -1; - private int mLastConfiguration = -1; - - // lists of enabled and disabled USB functions - private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); - private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); - - private boolean mSystemReady; - - private final Context mContext; - - private final UEventObserver mUEventObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "USB UEVENT: " + event.toString()); - } - - synchronized (this) { - String name = event.get("SWITCH_NAME"); - String state = event.get("SWITCH_STATE"); - if (name != null && state != null) { - try { - int intState = Integer.parseInt(state); - if ("usb_connected".equals(name)) { - mConnected = intState; - // trigger an Intent broadcast - if (mSystemReady) { - // debounce disconnects - update(mConnected == 0); - } - } else if ("usb_configuration".equals(name)) { - mConfiguration = intState; - // trigger an Intent broadcast - if (mSystemReady) { - update(mConnected == 0); - } - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); - } - } else { - String function = event.get("FUNCTION"); - String enabledStr = event.get("ENABLED"); - if (function != null && enabledStr != null) { - // Note: we do not broadcast a change when a function is enabled or disabled. - // We just record the state change for the next broadcast. - boolean enabled = "1".equals(enabledStr); - if (enabled) { - if (!mEnabledFunctions.contains(function)) { - mEnabledFunctions.add(function); - } - mDisabledFunctions.remove(function); - } else { - if (!mDisabledFunctions.contains(function)) { - mDisabledFunctions.add(function); - } - mEnabledFunctions.remove(function); - } - } - } - } - } - }; - - public UsbService(Context context) { - mContext = context; - init(); // set initial status - - if (mConfiguration >= 0) { - mUEventObserver.startObserving(USB_CONNECTED_MATCH); - mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); - mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); - } - } - - private final void init() { - char[] buffer = new char[1024]; - - mConfiguration = -1; - try { - FileReader file = new FileReader(USB_CONNECTED_PATH); - int len = file.read(buffer, 0, 1024); - file.close(); - mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); - - file = new FileReader(USB_CONFIGURATION_PATH); - len = file.read(buffer, 0, 1024); - file.close(); - mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); - } catch (FileNotFoundException e) { - Slog.i(TAG, "This kernel does not have USB configuration switch support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - if (mConfiguration < 0) - return; - - try { - File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); - for (int i = 0; i < files.length; i++) { - File file = new File(files[i], "enable"); - FileReader reader = new FileReader(file); - int len = reader.read(buffer, 0, 1024); - reader.close(); - int value = Integer.valueOf((new String(buffer, 0, len)).trim()); - String functionName = files[i].getName(); - if (value == 1) { - mEnabledFunctions.add(functionName); - } else { - mDisabledFunctions.add(functionName); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have USB composite class support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - } - - void systemReady() { - synchronized (this) { - update(false); - mSystemReady = true; - } - } - - private final void update(boolean delayed) { - mHandler.removeMessages(MSG_UPDATE); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0); - } - - private final Handler mHandler = new Handler() { - private void addEnabledFunctions(Intent intent) { - // include state of all USB functions in our extras - for (int i = 0; i < mEnabledFunctions.size(); i++) { - intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); - } - for (int i = 0; i < mDisabledFunctions.size(); i++) { - intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE: - synchronized (this) { - if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { - - final ContentResolver cr = mContext.getContentResolver(); - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); - return; - } - - mLastConnected = mConnected; - mLastConfiguration = mConfiguration; - - // send a sticky broadcast containing current USB state - Intent intent = new Intent(UsbManager.ACTION_USB_STATE); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); - intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); - addEnabledFunctions(intent); - mContext.sendStickyBroadcast(intent); - } - } - break; - } - } - }; -} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 7652a26..f774b29 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.hardware.UsbManager; +import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; 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..07fb1cc --- /dev/null +++ b/services/java/com/android/server/usb/UsbDeviceSettingsManager.java @@ -0,0 +1,551 @@ +/* + * 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.usb.UsbAccessory; +import android.hardware.usb.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 accessories + private final SparseArray<ArrayList<AccessoryFilter>> mAccessoryPermissionMap = + new SparseArray<ArrayList<AccessoryFilter>>(); + // Maps AccessoryFilter to user preferred application package + private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap = + new HashMap<AccessoryFilter, String>(); + + private final Object mLock = new Object(); + + // 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) { + synchronized (mLock) { + // clear all activity preferences for the package + if (clearPackageDefaultsLocked(packageName)) { + writeSettingsLocked(); + } + } + } + + public void onUidRemoved(int uid) { + synchronized (mLock) { + // clear all permissions for the UID + if (clearUidDefaultsLocked(uid)) { + writeSettingsLocked(); + } + } + } + } + MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); + + public UsbDeviceSettingsManager(Context context) { + mContext = context; + 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; + 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-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + mAccessoryPreferenceMap.put(filter, packageName); + } + XmlUtils.nextElement(parser); + } + + private void readSettingsLocked() { + 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 ("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 writeSettingsLocked() { + 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 = 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)); + 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 an accessory. + 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); + 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 (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> getAccessoryMatchesLocked( + 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 (packageMatchesLocked(resolveInfo, intent.getAction(), accessory)) { + matches.add(resolveInfo); + } + } + return matches; + } + + 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; + String defaultPackage; + synchronized (mLock) { + matches = getAccessoryMatchesLocked(accessory, accessoryIntent); + // 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)); + } + + 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(UsbAccessory accessory) { + if (accessory == null) return; + 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; + } + } + } + } + throw new SecurityException("User has not given permission to accessory " + accessory); + } + + public void setAccessoryPackage(UsbAccessory accessory, String packageName) { + AccessoryFilter filter = new AccessoryFilter(accessory); + boolean changed = false; + synchronized (mLock) { + if (packageName == null) { + changed = (mAccessoryPreferenceMap.remove(filter) != null); + } else { + changed = !packageName.equals(mAccessoryPreferenceMap.get(filter)); + if (changed) { + mAccessoryPreferenceMap.put(filter, packageName); + } + } + if (changed) { + writeSettingsLocked(); + } + } + } + + 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; + } + } + filterList.add(new AccessoryFilter(accessory)); + writeSettingsLocked(); + } + } + + public boolean hasDefaults(String packageName, int uid) { + synchronized (mLock) { + if (mAccessoryPermissionMap.get(uid) != null) return true; + if (mAccessoryPreferenceMap.values().contains(packageName)) return true; + return false; + } + } + + public void clearDefaults(String packageName, int uid) { + synchronized (mLock) { + boolean packageCleared = clearPackageDefaultsLocked(packageName); + boolean uidCleared = clearUidDefaultsLocked(uid); + if (packageCleared || uidCleared) { + 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) { + 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) { + 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); + } + } + 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..02669fd --- /dev/null +++ b/services/java/com/android/server/usb/UsbResolverActivity.java @@ -0,0 +1,91 @@ +/* + * 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.usb.IUsbManager; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.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_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/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java new file mode 100644 index 0000000..16e697c --- /dev/null +++ b/services/java/com/android/server/usb/UsbService.java @@ -0,0 +1,429 @@ +/* + * 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.server.usb; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.os.UEventObserver; +import android.provider.Settings; +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. + * This includes code for both USB host support (where the android device is the host) + * as well as USB device support (android device is connected to a USB host). + * 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. + */ +public class UsbService extends IUsbManager.Stub { + private static final String TAG = UsbService.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONNECTED_MATCH = + "DEVPATH=/devices/virtual/switch/usb_connected"; + private static final String USB_CONFIGURATION_MATCH = + "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = + "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONNECTED_PATH = + "/sys/class/switch/usb_connected/state"; + private static final String USB_CONFIGURATION_PATH = + "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = + "/sys/class/usb_composite"; + + private static final int MSG_UPDATE_STATE = 0; + private static final int MSG_FUNCTION_ENABLED = 1; + private static final int MSG_FUNCTION_DISABLED = 2; + + // Delay for debouncing USB disconnects. + // We often get rapid connect/disconnect events when enabling USB functions, + // which need debouncing. + private static final int UPDATE_DELAY = 1000; + + // current connected and configuration state + private int mConnected; + private int mConfiguration; + + // last broadcasted connected and configuration state + private int mLastConnected = -1; + private int mLastConfiguration = -1; + + // lists of enabled and disabled USB functions (for USB device mode) + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private UsbAccessory mCurrentAccessory; + // functions to restore after exiting accessory mode + private final ArrayList<String> mAccessoryRestoreFunctions = new ArrayList<String>(); + + private final Context mContext; + private final Object mLock = new Object(); + private final UsbDeviceSettingsManager mDeviceManager; + private final boolean mHasUsbAccessory; + + /* + * Handles USB function enable/disable events (device mode) + */ + private final void functionEnabledLocked(String function, boolean enabled) { + boolean enteringAccessoryMode = + (mHasUsbAccessory && enabled && UsbManager.USB_FUNCTION_ACCESSORY.equals(function)); + + if (enteringAccessoryMode) { + // keep a list of functions to reenable after exiting accessory mode + mAccessoryRestoreFunctions.clear(); + int count = mEnabledFunctions.size(); + for (int i = 0; i < count; i++) { + String f = mEnabledFunctions.get(i); + // RNDIS should not be restored and adb is handled automatically + if (!UsbManager.USB_FUNCTION_RNDIS.equals(f) && + !UsbManager.USB_FUNCTION_ADB.equals(f) && + !UsbManager.USB_FUNCTION_ACCESSORY.equals(f)) { + mAccessoryRestoreFunctions.add(f); + } + } + } + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + + if (enteringAccessoryMode) { + String[] strings = nativeGetAccessoryStrings(); + if (strings != null) { + mCurrentAccessory = new UsbAccessory(strings); + Log.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); + mDeviceManager.accessoryAttached(mCurrentAccessory); + } else { + Log.e(TAG, "nativeGetAccessoryStrings failed"); + } + } + } + + /* + * Listens for uevent messages from the kernel to monitor the USB state (device mode) + */ + private final UEventObserver mUEventObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (mLock) { + String name = event.get("SWITCH_NAME"); + String state = event.get("SWITCH_STATE"); + if (name != null && state != null) { + try { + int intState = Integer.parseInt(state); + if ("usb_connected".equals(name)) { + mConnected = intState; + // trigger an Intent broadcast + if (mSystemReady) { + // debounce disconnects to avoid problems bringing up USB tethering + update(mConnected == 0); + } + } else if ("usb_configuration".equals(name)) { + mConfiguration = intState; + // trigger an Intent broadcast + if (mSystemReady) { + update(mConnected == 0); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + int what = ("1".equals(enabledStr) ? + MSG_FUNCTION_ENABLED : MSG_FUNCTION_DISABLED); + Message msg = Message.obtain(mHandler, what); + msg.obj = function; + mHandler.sendMessage(msg); + } + } + } + } + }; + + public UsbService(Context context) { + mContext = context; + mDeviceManager = new UsbDeviceSettingsManager(context); + PackageManager pm = mContext.getPackageManager(); + mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); + + init(); // set initial status + + if (mConfiguration >= 0) { + mUEventObserver.startObserving(USB_CONNECTED_MATCH); + mUEventObserver.startObserving(USB_CONFIGURATION_MATCH); + mUEventObserver.startObserving(USB_FUNCTIONS_MATCH); + } + } + + private final void init() { + char[] buffer = new char[1024]; + + // Read initial USB state (device mode) + mConfiguration = -1; + try { + FileReader file = new FileReader(USB_CONNECTED_PATH); + int len = file.read(buffer, 0, 1024); + file.close(); + mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); + + file = new FileReader(USB_CONFIGURATION_PATH); + len = file.read(buffer, 0, 1024); + file.close(); + mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.i(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + if (mConfiguration < 0) + return; + + // Read initial list of enabled and disabled functions (device mode) + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + reader.close(); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + public void systemReady() { + synchronized (mLock) { + update(false); + mSystemReady = true; + } + } + + /* + * Sends a message to update the USB connected and configured state (device mode). + * If delayed is true, then we add a small delay in sending the message to debounce + * the USB connection when enabling USB tethering. + */ + private final void update(boolean delayed) { + mHandler.removeMessages(MSG_UPDATE_STATE); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATE, delayed ? UPDATE_DELAY : 0); + } + + /* returns the currently attached USB accessory (device mode) */ + public UsbAccessory getCurrentAccessory() { + synchronized (mLock) { + mDeviceManager.checkPermission(mCurrentAccessory); + return mCurrentAccessory; + } + } + + /* opens the currently attached USB accessory (device mode) */ + 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 setAccessoryPackage(UsbAccessory accessory, String packageName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.setAccessoryPackage(accessory, packageName); + } + + 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) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + return mDeviceManager.hasDefaults(packageName, uid); + } + + public void clearDefaults(String packageName, int uid) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.clearDefaults(packageName, uid); + } + + /* + * This handler is for deferred handling of events related to device mode and accessories. + */ + private final Handler mHandler = new Handler() { + private void addEnabledFunctionsLocked(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + synchronized (mLock) { + switch (msg.what) { + case MSG_UPDATE_STATE: + if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { + if (mConnected == 0 && mCurrentAccessory != null) { + // turn off accessory mode when we are disconnected + if (UsbManager.setFunctionEnabled( + UsbManager.USB_FUNCTION_ACCESSORY, false)) { + Log.d(TAG, "exited USB accessory mode"); + + // restore previously enabled functions + for (String function : mAccessoryRestoreFunctions) { + if (UsbManager.setFunctionEnabled(function, true)) { + Log.e(TAG, "could not reenable function " + function); + } + } + mAccessoryRestoreFunctions.clear(); + + mDeviceManager.accessoryDetached(mCurrentAccessory); + mCurrentAccessory = null; + + // this will cause an immediate reset of the USB bus, + // so there is no point in sending the + // function disabled broadcast. + return; + } else { + Log.e(TAG, "could not disable USB_FUNCTION_ACCESSORY"); + } + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + + mLastConnected = mConnected; + mLastConfiguration = mConfiguration; + + // send a sticky broadcast containing current USB state + Intent intent = new Intent(UsbManager.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0); + intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration); + addEnabledFunctionsLocked(intent); + mContext.sendStickyBroadcast(intent); + } + break; + case MSG_FUNCTION_ENABLED: + case MSG_FUNCTION_DISABLED: + functionEnabledLocked((String)msg.obj, msg.what == MSG_FUNCTION_ENABLED); + break; + } + } + } + }; + + @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(" mCurrentAccessory: " + mCurrentAccessory); + + mDeviceManager.dump(fd, pw); + } + } + + // accessory support + private native String[] nativeGetAccessoryStrings(); + private native ParcelFileDescriptor nativeOpenAccessory(); +} |