summaryrefslogtreecommitdiffstats
path: root/services/usb
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2014-02-19 19:48:05 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2014-02-19 19:48:05 +0000
commita78068e7a3af53cc2b590902f74074d1781513da (patch)
tree51e9324e75740ea3bec3f3c32d557af2eb15bfe3 /services/usb
parent55b8ddc30386ec15073304b1f9fabc7eb789f52e (diff)
parent13c839962e246517a7a95326610ea30c529d79dc (diff)
downloadframeworks_base-a78068e7a3af53cc2b590902f74074d1781513da.zip
frameworks_base-a78068e7a3af53cc2b590902f74074d1781513da.tar.gz
frameworks_base-a78068e7a3af53cc2b590902f74074d1781513da.tar.bz2
am 13c83996: am 1c532c2e: am 4b07b26e: Merge "Make UsbService optional" into klp-modular-dev
* commit '13c839962e246517a7a95326610ea30c529d79dc': Make UsbService optional
Diffstat (limited to 'services/usb')
-rw-r--r--services/usb/Android.mk12
-rw-r--r--services/usb/java/com/android/server/usb/UsbDebuggingManager.java342
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java883
-rw-r--r--services/usb/java/com/android/server/usb/UsbHostManager.java222
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java311
-rw-r--r--services/usb/java/com/android/server/usb/UsbSettingsManager.java1215
6 files changed, 2985 insertions, 0 deletions
diff --git a/services/usb/Android.mk b/services/usb/Android.mk
new file mode 100644
index 0000000..feabf0a
--- /dev/null
+++ b/services/usb/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.usb
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
new file mode 100644
index 0000000..ce953a4
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2012 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Handler;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.Base64;
+import com.android.server.FgThread;
+
+import java.lang.Thread;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+public class UsbDebuggingManager implements Runnable {
+ private static final String TAG = "UsbDebuggingManager";
+ private static final boolean DEBUG = false;
+
+ private final String ADBD_SOCKET = "adbd";
+ private final String ADB_DIRECTORY = "misc/adb";
+ private final String ADB_KEYS_FILE = "adb_keys";
+ private final int BUFFER_SIZE = 4096;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private Thread mThread;
+ private boolean mAdbEnabled = false;
+ private String mFingerprints;
+ private LocalSocket mSocket = null;
+ private OutputStream mOutputStream = null;
+
+ public UsbDebuggingManager(Context context) {
+ mHandler = new UsbDebuggingHandler(FgThread.get().getLooper());
+ mContext = context;
+ }
+
+ private void listenToSocket() throws IOException {
+ try {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
+ LocalSocketAddress.Namespace.RESERVED);
+ InputStream inputStream = null;
+
+ mSocket = new LocalSocket();
+ mSocket.connect(address);
+
+ mOutputStream = mSocket.getOutputStream();
+ inputStream = mSocket.getInputStream();
+
+ while (true) {
+ int count = inputStream.read(buffer);
+ if (count < 0) {
+ Slog.e(TAG, "got " + count + " reading");
+ break;
+ }
+
+ if (buffer[0] == 'P' && buffer[1] == 'K') {
+ String key = new String(Arrays.copyOfRange(buffer, 2, count));
+ Slog.d(TAG, "Received public key: " + key);
+ Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ }
+ else {
+ Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2))));
+ break;
+ }
+ }
+ } catch (IOException ex) {
+ Slog.e(TAG, "Communication error: ", ex);
+ throw ex;
+ } finally {
+ closeSocket();
+ }
+ }
+
+ @Override
+ public void run() {
+ while (mAdbEnabled) {
+ try {
+ listenToSocket();
+ } catch (Exception e) {
+ /* Don't loop too fast if adbd dies, before init restarts it */
+ SystemClock.sleep(1000);
+ }
+ }
+ }
+
+ private void closeSocket() {
+ try {
+ mOutputStream.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed closing output stream: " + e);
+ }
+
+ try {
+ mSocket.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed closing socket: " + ex);
+ }
+ }
+
+ private void sendResponse(String msg) {
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.write(msg.getBytes());
+ }
+ catch (IOException ex) {
+ Slog.e(TAG, "Failed to write response:", ex);
+ }
+ }
+ }
+
+ class UsbDebuggingHandler extends Handler {
+ private static final int MESSAGE_ADB_ENABLED = 1;
+ private static final int MESSAGE_ADB_DISABLED = 2;
+ private static final int MESSAGE_ADB_ALLOW = 3;
+ private static final int MESSAGE_ADB_DENY = 4;
+ private static final int MESSAGE_ADB_CONFIRM = 5;
+ private static final int MESSAGE_ADB_CLEAR = 6;
+
+ public UsbDebuggingHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_ADB_ENABLED:
+ if (mAdbEnabled)
+ break;
+
+ mAdbEnabled = true;
+
+ mThread = new Thread(UsbDebuggingManager.this, TAG);
+ mThread.start();
+
+ break;
+
+ case MESSAGE_ADB_DISABLED:
+ if (!mAdbEnabled)
+ break;
+
+ mAdbEnabled = false;
+ closeSocket();
+
+ try {
+ mThread.join();
+ } catch (Exception ex) {
+ }
+
+ mThread = null;
+ mOutputStream = null;
+ mSocket = null;
+ break;
+
+ case MESSAGE_ADB_ALLOW: {
+ String key = (String)msg.obj;
+ String fingerprints = getFingerprints(key);
+
+ if (!fingerprints.equals(mFingerprints)) {
+ Slog.e(TAG, "Fingerprints do not match. Got "
+ + fingerprints + ", expected " + mFingerprints);
+ break;
+ }
+
+ if (msg.arg1 == 1) {
+ writeKey(key);
+ }
+
+ sendResponse("OK");
+ break;
+ }
+
+ case MESSAGE_ADB_DENY:
+ sendResponse("NO");
+ break;
+
+ case MESSAGE_ADB_CONFIRM: {
+ String key = (String)msg.obj;
+ mFingerprints = getFingerprints(key);
+ showConfirmationDialog(key, mFingerprints);
+ break;
+ }
+
+ case MESSAGE_ADB_CLEAR:
+ deleteKeyFile();
+ break;
+ }
+ }
+ }
+
+ private String getFingerprints(String key) {
+ String hex = "0123456789ABCDEF";
+ StringBuilder sb = new StringBuilder();
+ MessageDigest digester;
+
+ try {
+ digester = MessageDigest.getInstance("MD5");
+ } catch (Exception ex) {
+ Slog.e(TAG, "Error getting digester: " + ex);
+ return "";
+ }
+
+ byte[] base64_data = key.split("\\s+")[0].getBytes();
+ byte[] digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT));
+
+ for (int i = 0; i < digest.length; i++) {
+ sb.append(hex.charAt((digest[i] >> 4) & 0xf));
+ sb.append(hex.charAt(digest[i] & 0xf));
+ if (i < digest.length - 1)
+ sb.append(":");
+ }
+ return sb.toString();
+ }
+
+ private void showConfirmationDialog(String key, String fingerprints) {
+ Intent dialogIntent = new Intent();
+
+ dialogIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbDebuggingActivity");
+ dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ dialogIntent.putExtra("key", key);
+ dialogIntent.putExtra("fingerprints", fingerprints);
+ try {
+ mContext.startActivity(dialogIntent);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start UsbDebuggingActivity");
+ }
+ }
+
+ private File getUserKeyFile() {
+ File dataDir = Environment.getDataDirectory();
+ File adbDir = new File(dataDir, ADB_DIRECTORY);
+
+ if (!adbDir.exists()) {
+ Slog.e(TAG, "ADB data directory does not exist");
+ return null;
+ }
+
+ return new File(adbDir, ADB_KEYS_FILE);
+ }
+
+ private void writeKey(String key) {
+ try {
+ File keyFile = getUserKeyFile();
+
+ if (keyFile == null) {
+ return;
+ }
+
+ if (!keyFile.exists()) {
+ keyFile.createNewFile();
+ FileUtils.setPermissions(keyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+ FileUtils.S_IRGRP, -1, -1);
+ }
+
+ FileOutputStream fo = new FileOutputStream(keyFile, true);
+ fo.write(key.getBytes());
+ fo.write('\n');
+ fo.close();
+ }
+ catch (IOException ex) {
+ Slog.e(TAG, "Error writing key:" + ex);
+ }
+ }
+
+ private void deleteKeyFile() {
+ File keyFile = getUserKeyFile();
+ if (keyFile != null) {
+ keyFile.delete();
+ }
+ }
+
+ public void setAdbEnabled(boolean enabled) {
+ mHandler.sendEmptyMessage(enabled ? UsbDebuggingHandler.MESSAGE_ADB_ENABLED
+ : UsbDebuggingHandler.MESSAGE_ADB_DISABLED);
+ }
+
+ public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+ Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_ALLOW);
+ msg.arg1 = alwaysAllow ? 1 : 0;
+ msg.obj = publicKey;
+ mHandler.sendMessage(msg);
+ }
+
+ public void denyUsbDebugging() {
+ mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_DENY);
+ }
+
+ public void clearUsbDebuggingKeys() {
+ mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_CLEAR);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw) {
+ pw.println(" USB Debugging State:");
+ pw.println(" Connected to adbd: " + (mOutputStream != null));
+ pw.println(" Last key received: " + mFingerprints);
+ pw.println(" User keys:");
+ try {
+ pw.println(FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
+ } catch (IOException e) {
+ pw.println("IOException: " + e);
+ }
+ pw.println(" System keys:");
+ try {
+ pw.println(FileUtils.readTextFile(new File("/adb_keys"), 0, null));
+ } catch (IOException e) {
+ pw.println("IOException: " + e);
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
new file mode 100644
index 0000000..d5dd9a6
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -0,0 +1,883 @@
+/*
+ * 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.Settings;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.FgThread;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Scanner;
+
+/**
+ * UsbDeviceManager manages USB state in device mode.
+ */
+public class UsbDeviceManager {
+
+ private static final String TAG = UsbDeviceManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String USB_STATE_MATCH =
+ "DEVPATH=/devices/virtual/android_usb/android0";
+ private static final String ACCESSORY_START_MATCH =
+ "DEVPATH=/devices/virtual/misc/usb_accessory";
+ private static final String FUNCTIONS_PATH =
+ "/sys/class/android_usb/android0/functions";
+ private static final String STATE_PATH =
+ "/sys/class/android_usb/android0/state";
+ private static final String MASS_STORAGE_FILE_PATH =
+ "/sys/class/android_usb/android0/f_mass_storage/lun/file";
+ private static final String RNDIS_ETH_ADDR_PATH =
+ "/sys/class/android_usb/android0/f_rndis/ethaddr";
+ private static final String AUDIO_SOURCE_PCM_PATH =
+ "/sys/class/android_usb/android0/f_audio_source/pcm";
+
+ private static final int MSG_UPDATE_STATE = 0;
+ private static final int MSG_ENABLE_ADB = 1;
+ private static final int MSG_SET_CURRENT_FUNCTIONS = 2;
+ private static final int MSG_SYSTEM_READY = 3;
+ private static final int MSG_BOOT_COMPLETED = 4;
+ private static final int MSG_USER_SWITCHED = 5;
+
+ private static final int AUDIO_MODE_NONE = 0;
+ private static final int AUDIO_MODE_SOURCE = 1;
+
+ // 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;
+
+ private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
+
+ private UsbHandler mHandler;
+ private boolean mBootCompleted;
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ @GuardedBy("mLock")
+ private UsbSettingsManager mCurrentSettings;
+ private NotificationManager mNotificationManager;
+ private final boolean mHasUsbAccessory;
+ private boolean mUseUsbNotification;
+ private boolean mAdbEnabled;
+ private boolean mAudioSourceEnabled;
+ private Map<String, List<Pair<String, String>>> mOemModeMap;
+ private String[] mAccessoryStrings;
+ private UsbDebuggingManager mDebuggingManager;
+
+ private class AdbSettingsObserver extends ContentObserver {
+ public AdbSettingsObserver() {
+ super(null);
+ }
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean enable = (Settings.Global.getInt(mContentResolver,
+ Settings.Global.ADB_ENABLED, 0) > 0);
+ mHandler.sendMessage(MSG_ENABLE_ADB, enable);
+ }
+ }
+
+ /*
+ * Listens for uevent messages from the kernel to monitor the USB state
+ */
+ private final UEventObserver mUEventObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
+
+ String state = event.get("USB_STATE");
+ String accessory = event.get("ACCESSORY");
+ if (state != null) {
+ mHandler.updateState(state);
+ } else if ("START".equals(accessory)) {
+ if (DEBUG) Slog.d(TAG, "got accessory start");
+ startAccessoryMode();
+ }
+ }
+ };
+
+ public UsbDeviceManager(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ PackageManager pm = mContext.getPackageManager();
+ mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
+ initRndisAddress();
+
+ readOemUsbOverrideConfig();
+
+ mHandler = new UsbHandler(FgThread.get().getLooper());
+
+ if (nativeIsStartRequested()) {
+ if (DEBUG) Slog.d(TAG, "accessory attached at boot");
+ startAccessoryMode();
+ }
+
+ boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
+ boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
+ if (secureAdbEnabled && !dataEncrypted) {
+ mDebuggingManager = new UsbDebuggingManager(context);
+ }
+ }
+
+ public void setCurrentSettings(UsbSettingsManager settings) {
+ synchronized (mLock) {
+ mCurrentSettings = settings;
+ }
+ }
+
+ private UsbSettingsManager getCurrentSettings() {
+ synchronized (mLock) {
+ return mCurrentSettings;
+ }
+ }
+
+ public void systemReady() {
+ if (DEBUG) Slog.d(TAG, "systemReady");
+
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // We do not show the USB notification if the primary volume supports mass storage.
+ // The legacy mass storage UI will be used instead.
+ boolean massStorageSupported = false;
+ final StorageManager storageManager = StorageManager.from(mContext);
+ final StorageVolume primary = storageManager.getPrimaryVolume();
+ massStorageSupported = primary != null && primary.allowMassStorage();
+ mUseUsbNotification = !massStorageSupported;
+
+ // make sure the ADB_ENABLED setting value matches the current state
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+
+ mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+ }
+
+ private void startAccessoryMode() {
+ mAccessoryStrings = nativeGetAccessoryStrings();
+ boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
+ // don't start accessory mode if our mandatory strings have not been set
+ boolean enableAccessory = (mAccessoryStrings != null &&
+ mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
+ mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
+ String functions = null;
+
+ if (enableAccessory && enableAudio) {
+ functions = UsbManager.USB_FUNCTION_ACCESSORY + ","
+ + UsbManager.USB_FUNCTION_AUDIO_SOURCE;
+ } else if (enableAccessory) {
+ functions = UsbManager.USB_FUNCTION_ACCESSORY;
+ } else if (enableAudio) {
+ functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE;
+ }
+
+ if (functions != null) {
+ setCurrentFunctions(functions, false);
+ }
+ }
+
+ private static void initRndisAddress() {
+ // configure RNDIS ethernet address based on our serial number using the same algorithm
+ // we had been previously using in kernel board files
+ final int ETH_ALEN = 6;
+ int address[] = new int[ETH_ALEN];
+ // first byte is 0x02 to signify a locally administered address
+ address[0] = 0x02;
+
+ String serial = SystemProperties.get("ro.serialno", "1234567890ABCDEF");
+ int serialLength = serial.length();
+ // XOR the USB serial across the remaining 5 bytes
+ for (int i = 0; i < serialLength; i++) {
+ address[i % (ETH_ALEN - 1) + 1] ^= (int)serial.charAt(i);
+ }
+ String addrString = String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
+ address[0], address[1], address[2], address[3], address[4], address[5]);
+ try {
+ FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString);
+ } catch (IOException e) {
+ Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
+ }
+ }
+
+ private static String addFunction(String functions, String function) {
+ if ("none".equals(functions)) {
+ return function;
+ }
+ if (!containsFunction(functions, function)) {
+ if (functions.length() > 0) {
+ functions += ",";
+ }
+ functions += function;
+ }
+ return functions;
+ }
+
+ private static String removeFunction(String functions, String function) {
+ String[] split = functions.split(",");
+ for (int i = 0; i < split.length; i++) {
+ if (function.equals(split[i])) {
+ split[i] = null;
+ }
+ }
+ if (split.length == 1 && split[0] == null) {
+ return "none";
+ }
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < split.length; i++) {
+ String s = split[i];
+ if (s != null) {
+ if (builder.length() > 0) {
+ builder.append(",");
+ }
+ builder.append(s);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static boolean containsFunction(String functions, String function) {
+ return Arrays.asList(functions.split(",")).contains(function);
+ }
+
+ private final class UsbHandler extends Handler {
+
+ // current USB state
+ private boolean mConnected;
+ private boolean mConfigured;
+ private String mCurrentFunctions;
+ private String mDefaultFunctions;
+ private UsbAccessory mCurrentAccessory;
+ private int mUsbNotificationId;
+ private boolean mAdbNotificationShown;
+ private int mCurrentUser = UserHandle.USER_NULL;
+
+ private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "boot completed");
+ mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
+ }
+ };
+
+ private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ mHandler.obtainMessage(MSG_USER_SWITCHED, userId, 0).sendToTarget();
+ }
+ };
+
+ public UsbHandler(Looper looper) {
+ super(looper);
+ try {
+ // persist.sys.usb.config should never be unset. But if it is, set it to "adb"
+ // so we have a chance of debugging what happened.
+ mDefaultFunctions = SystemProperties.get("persist.sys.usb.config", "adb");
+
+ // Check if USB mode needs to be overridden depending on OEM specific bootmode.
+ mDefaultFunctions = processOemUsbOverride(mDefaultFunctions);
+
+ // sanity check the sys.usb.config system property
+ // this may be necessary if we crashed while switching USB configurations
+ String config = SystemProperties.get("sys.usb.config", "none");
+ if (!config.equals(mDefaultFunctions)) {
+ Slog.w(TAG, "resetting config to persistent property: " + mDefaultFunctions);
+ SystemProperties.set("sys.usb.config", mDefaultFunctions);
+ }
+
+ mCurrentFunctions = mDefaultFunctions;
+ String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
+ updateState(state);
+ mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
+
+ // Upgrade step for previous versions that used persist.service.adb.enable
+ String value = SystemProperties.get("persist.service.adb.enable", "");
+ if (value.length() > 0) {
+ char enable = value.charAt(0);
+ if (enable == '1') {
+ setAdbEnabled(true);
+ } else if (enable == '0') {
+ setAdbEnabled(false);
+ }
+ SystemProperties.set("persist.service.adb.enable", "");
+ }
+
+ // register observer to listen for settings changes
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+ false, new AdbSettingsObserver());
+
+ // Watch for USB configuration changes
+ mUEventObserver.startObserving(USB_STATE_MATCH);
+ mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+
+ mContext.registerReceiver(
+ mBootCompletedReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ mContext.registerReceiver(
+ mUserSwitchedReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
+ } catch (Exception e) {
+ Slog.e(TAG, "Error initializing UsbHandler", e);
+ }
+ }
+
+ public void sendMessage(int what, boolean arg) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = (arg ? 1 : 0);
+ sendMessage(m);
+ }
+
+ public void sendMessage(int what, Object arg) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ sendMessage(m);
+ }
+
+ public void sendMessage(int what, Object arg0, boolean arg1) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg0;
+ m.arg1 = (arg1 ? 1 : 0);
+ sendMessage(m);
+ }
+
+ public void updateState(String state) {
+ int connected, configured;
+
+ if ("DISCONNECTED".equals(state)) {
+ connected = 0;
+ configured = 0;
+ } else if ("CONNECTED".equals(state)) {
+ connected = 1;
+ configured = 0;
+ } else if ("CONFIGURED".equals(state)) {
+ connected = 1;
+ configured = 1;
+ } else {
+ Slog.e(TAG, "unknown state " + state);
+ return;
+ }
+ removeMessages(MSG_UPDATE_STATE);
+ Message msg = Message.obtain(this, MSG_UPDATE_STATE);
+ msg.arg1 = connected;
+ msg.arg2 = configured;
+ // debounce disconnects to avoid problems bringing up USB tethering
+ sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
+ }
+
+ private boolean waitForState(String state) {
+ // wait for the transition to complete.
+ // give up after 1 second.
+ for (int i = 0; i < 20; i++) {
+ // State transition is done when sys.usb.state is set to the new configuration
+ if (state.equals(SystemProperties.get("sys.usb.state"))) return true;
+ SystemClock.sleep(50);
+ }
+ Slog.e(TAG, "waitForState(" + state + ") FAILED");
+ return false;
+ }
+
+ private boolean setUsbConfig(String config) {
+ if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
+ // set the new configuration
+ SystemProperties.set("sys.usb.config", config);
+ return waitForState(config);
+ }
+
+ private void setAdbEnabled(boolean enable) {
+ if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
+ if (enable != mAdbEnabled) {
+ mAdbEnabled = enable;
+ // Due to the persist.sys.usb.config property trigger, changing adb state requires
+ // switching to default function
+ setEnabledFunctions(mDefaultFunctions, true);
+ updateAdbNotification();
+ }
+ if (mDebuggingManager != null) {
+ mDebuggingManager.setAdbEnabled(mAdbEnabled);
+ }
+ }
+
+ private void setEnabledFunctions(String functions, boolean makeDefault) {
+
+ // Do not update persystent.sys.usb.config if the device is booted up
+ // with OEM specific mode.
+ if (functions != null && makeDefault && !needsOemUsbOverride()) {
+
+ if (mAdbEnabled) {
+ functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
+ } else {
+ functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
+ }
+ if (!mDefaultFunctions.equals(functions)) {
+ if (!setUsbConfig("none")) {
+ Slog.e(TAG, "Failed to disable USB");
+ // revert to previous configuration if we fail
+ setUsbConfig(mCurrentFunctions);
+ return;
+ }
+ // setting this property will also change the current USB state
+ // via a property trigger
+ SystemProperties.set("persist.sys.usb.config", functions);
+ if (waitForState(functions)) {
+ mCurrentFunctions = functions;
+ mDefaultFunctions = functions;
+ } else {
+ Slog.e(TAG, "Failed to switch persistent USB config to " + functions);
+ // revert to previous configuration if we fail
+ SystemProperties.set("persist.sys.usb.config", mDefaultFunctions);
+ }
+ }
+ } else {
+ if (functions == null) {
+ functions = mDefaultFunctions;
+ }
+
+ // Override with bootmode specific usb mode if needed
+ functions = processOemUsbOverride(functions);
+
+ if (mAdbEnabled) {
+ functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
+ } else {
+ functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
+ }
+ if (!mCurrentFunctions.equals(functions)) {
+ if (!setUsbConfig("none")) {
+ Slog.e(TAG, "Failed to disable USB");
+ // revert to previous configuration if we fail
+ setUsbConfig(mCurrentFunctions);
+ return;
+ }
+ if (setUsbConfig(functions)) {
+ mCurrentFunctions = functions;
+ } else {
+ Slog.e(TAG, "Failed to switch USB config to " + functions);
+ // revert to previous configuration if we fail
+ setUsbConfig(mCurrentFunctions);
+ }
+ }
+ }
+ }
+
+ private void updateCurrentAccessory() {
+ if (!mHasUsbAccessory) return;
+
+ if (mConfigured) {
+ if (mAccessoryStrings != null) {
+ mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
+ Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
+ // defer accessoryAttached if system is not ready
+ if (mBootCompleted) {
+ getCurrentSettings().accessoryAttached(mCurrentAccessory);
+ } // else handle in mBootCompletedReceiver
+ } else {
+ Slog.e(TAG, "nativeGetAccessoryStrings failed");
+ }
+ } else if (!mConnected) {
+ // make sure accessory mode is off
+ // and restore default functions
+ Slog.d(TAG, "exited USB accessory mode");
+ setEnabledFunctions(mDefaultFunctions, false);
+
+ if (mCurrentAccessory != null) {
+ if (mBootCompleted) {
+ getCurrentSettings().accessoryDetached(mCurrentAccessory);
+ }
+ mCurrentAccessory = null;
+ mAccessoryStrings = null;
+ }
+ }
+ }
+
+ private void updateUsbState() {
+ // 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);
+ intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
+
+ if (mCurrentFunctions != null) {
+ String[] functions = mCurrentFunctions.split(",");
+ for (int i = 0; i < functions.length; i++) {
+ intent.putExtra(functions[i], true);
+ }
+ }
+
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void updateAudioSourceFunction() {
+ boolean enabled = containsFunction(mCurrentFunctions,
+ UsbManager.USB_FUNCTION_AUDIO_SOURCE);
+ if (enabled != mAudioSourceEnabled) {
+ // send a sticky broadcast containing current USB state
+ Intent intent = new Intent(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra("state", (enabled ? 1 : 0));
+ if (enabled) {
+ try {
+ Scanner scanner = new Scanner(new File(AUDIO_SOURCE_PCM_PATH));
+ int card = scanner.nextInt();
+ int device = scanner.nextInt();
+ intent.putExtra("card", card);
+ intent.putExtra("device", device);
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "could not open audio source PCM file", e);
+ }
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mAudioSourceEnabled = enabled;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_STATE:
+ mConnected = (msg.arg1 == 1);
+ mConfigured = (msg.arg2 == 1);
+ updateUsbNotification();
+ updateAdbNotification();
+ if (containsFunction(mCurrentFunctions,
+ UsbManager.USB_FUNCTION_ACCESSORY)) {
+ updateCurrentAccessory();
+ }
+
+ if (!mConnected) {
+ // restore defaults when USB is disconnected
+ setEnabledFunctions(mDefaultFunctions, false);
+ }
+ if (mBootCompleted) {
+ updateUsbState();
+ updateAudioSourceFunction();
+ }
+ break;
+ case MSG_ENABLE_ADB:
+ setAdbEnabled(msg.arg1 == 1);
+ break;
+ case MSG_SET_CURRENT_FUNCTIONS:
+ String functions = (String)msg.obj;
+ boolean makeDefault = (msg.arg1 == 1);
+ setEnabledFunctions(functions, makeDefault);
+ break;
+ case MSG_SYSTEM_READY:
+ updateUsbNotification();
+ updateAdbNotification();
+ updateUsbState();
+ updateAudioSourceFunction();
+ break;
+ case MSG_BOOT_COMPLETED:
+ mBootCompleted = true;
+ if (mCurrentAccessory != null) {
+ getCurrentSettings().accessoryAttached(mCurrentAccessory);
+ }
+ if (mDebuggingManager != null) {
+ mDebuggingManager.setAdbEnabled(mAdbEnabled);
+ }
+ break;
+ case MSG_USER_SWITCHED: {
+ final boolean mtpActive =
+ containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
+ || containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
+ if (mtpActive && mCurrentUser != UserHandle.USER_NULL) {
+ Slog.v(TAG, "Current user switched; resetting USB host stack for MTP");
+ setUsbConfig("none");
+ setUsbConfig(mCurrentFunctions);
+ }
+ mCurrentUser = msg.arg1;
+ break;
+ }
+ }
+ }
+
+ public UsbAccessory getCurrentAccessory() {
+ return mCurrentAccessory;
+ }
+
+ private void updateUsbNotification() {
+ if (mNotificationManager == null || !mUseUsbNotification) return;
+ int id = 0;
+ Resources r = mContext.getResources();
+ if (mConnected) {
+ if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) {
+ id = com.android.internal.R.string.usb_mtp_notification_title;
+ } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) {
+ id = com.android.internal.R.string.usb_ptp_notification_title;
+ } else if (containsFunction(mCurrentFunctions,
+ UsbManager.USB_FUNCTION_MASS_STORAGE)) {
+ id = com.android.internal.R.string.usb_cd_installer_notification_title;
+ } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) {
+ id = com.android.internal.R.string.usb_accessory_notification_title;
+ } else {
+ // There is a different notification for USB tethering so we don't need one here
+ //if (!containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_RNDIS)) {
+ // Slog.e(TAG, "No known USB function in updateUsbNotification");
+ //}
+ }
+ }
+ if (id != mUsbNotificationId) {
+ // clear notification if title needs changing
+ if (mUsbNotificationId != 0) {
+ mNotificationManager.cancelAsUser(null, mUsbNotificationId,
+ UserHandle.ALL);
+ mUsbNotificationId = 0;
+ }
+ if (id != 0) {
+ CharSequence message = r.getText(
+ com.android.internal.R.string.usb_notification_message);
+ CharSequence title = r.getText(id);
+
+ Notification notification = new Notification();
+ notification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
+ notification.when = 0;
+ notification.flags = Notification.FLAG_ONGOING_EVENT;
+ notification.tickerText = title;
+ notification.defaults = 0; // please be quiet
+ notification.sound = null;
+ notification.vibrate = null;
+ notification.priority = Notification.PRIORITY_MIN;
+
+ Intent intent = Intent.makeRestartActivityTask(
+ new ComponentName("com.android.settings",
+ "com.android.settings.UsbSettings"));
+ PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, 0, null, UserHandle.CURRENT);
+ notification.setLatestEventInfo(mContext, title, message, pi);
+ notification.visibility = Notification.VISIBILITY_PUBLIC;
+ mNotificationManager.notifyAsUser(null, id, notification,
+ UserHandle.ALL);
+ mUsbNotificationId = id;
+ }
+ }
+ }
+
+ private void updateAdbNotification() {
+ if (mNotificationManager == null) return;
+ final int id = com.android.internal.R.string.adb_active_notification_title;
+ if (mAdbEnabled && mConnected) {
+ if ("0".equals(SystemProperties.get("persist.adb.notify"))) return;
+
+ if (!mAdbNotificationShown) {
+ Resources r = mContext.getResources();
+ CharSequence title = r.getText(id);
+ CharSequence message = r.getText(
+ com.android.internal.R.string.adb_active_notification_message);
+
+ Notification notification = new Notification();
+ notification.icon = com.android.internal.R.drawable.stat_sys_adb;
+ notification.when = 0;
+ notification.flags = Notification.FLAG_ONGOING_EVENT;
+ notification.tickerText = title;
+ notification.defaults = 0; // please be quiet
+ notification.sound = null;
+ notification.vibrate = null;
+ notification.priority = Notification.PRIORITY_LOW;
+
+ Intent intent = Intent.makeRestartActivityTask(
+ new ComponentName("com.android.settings",
+ "com.android.settings.DevelopmentSettings"));
+ PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, 0, null, UserHandle.CURRENT);
+ notification.setLatestEventInfo(mContext, title, message, pi);
+ notification.visibility = Notification.VISIBILITY_PUBLIC;
+ mAdbNotificationShown = true;
+ mNotificationManager.notifyAsUser(null, id, notification,
+ UserHandle.ALL);
+ }
+ } else if (mAdbNotificationShown) {
+ mAdbNotificationShown = false;
+ mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw) {
+ pw.println(" USB Device State:");
+ pw.println(" Current Functions: " + mCurrentFunctions);
+ pw.println(" Default Functions: " + mDefaultFunctions);
+ pw.println(" mConnected: " + mConnected);
+ pw.println(" mConfigured: " + mConfigured);
+ pw.println(" mCurrentAccessory: " + mCurrentAccessory);
+ try {
+ pw.println(" Kernel state: "
+ + FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim());
+ pw.println(" Kernel function list: "
+ + FileUtils.readTextFile(new File(FUNCTIONS_PATH), 0, null).trim());
+ pw.println(" Mass storage backing file: "
+ + FileUtils.readTextFile(new File(MASS_STORAGE_FILE_PATH), 0, null).trim());
+ } catch (IOException e) {
+ pw.println("IOException: " + e);
+ }
+ }
+ }
+
+ /* returns the currently attached USB accessory */
+ public UsbAccessory getCurrentAccessory() {
+ return mHandler.getCurrentAccessory();
+ }
+
+ /* opens the currently attached USB accessory */
+ public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+ UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
+ if (currentAccessory == null) {
+ throw new IllegalArgumentException("no accessory attached");
+ }
+ if (!currentAccessory.equals(accessory)) {
+ String error = accessory.toString()
+ + " does not match current accessory "
+ + currentAccessory;
+ throw new IllegalArgumentException(error);
+ }
+ getCurrentSettings().checkPermission(accessory);
+ return nativeOpenAccessory();
+ }
+
+ public void setCurrentFunctions(String functions, boolean makeDefault) {
+ if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ") default: " + makeDefault);
+ mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, makeDefault);
+ }
+
+ public void setMassStorageBackingFile(String path) {
+ if (path == null) path = "";
+ try {
+ FileUtils.stringToFile(MASS_STORAGE_FILE_PATH, path);
+ } catch (IOException e) {
+ Slog.e(TAG, "failed to write to " + MASS_STORAGE_FILE_PATH);
+ }
+ }
+
+ private void readOemUsbOverrideConfig() {
+ String[] configList = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_oemUsbModeOverride);
+
+ if (configList != null) {
+ for (String config: configList) {
+ String[] items = config.split(":");
+ if (items.length == 3) {
+ if (mOemModeMap == null) {
+ mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
+ }
+ List<Pair<String, String>> overrideList = mOemModeMap.get(items[0]);
+ if (overrideList == null) {
+ overrideList = new LinkedList<Pair<String, String>>();
+ mOemModeMap.put(items[0], overrideList);
+ }
+ overrideList.add(new Pair<String, String>(items[1], items[2]));
+ }
+ }
+ }
+ }
+
+ private boolean needsOemUsbOverride() {
+ if (mOemModeMap == null) return false;
+
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ return (mOemModeMap.get(bootMode) != null) ? true : false;
+ }
+
+ private String processOemUsbOverride(String usbFunctions) {
+ if ((usbFunctions == null) || (mOemModeMap == null)) return usbFunctions;
+
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+
+ List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
+ if (overrides != null) {
+ for (Pair<String, String> pair: overrides) {
+ if (pair.first.equals(usbFunctions)) {
+ Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
+ return pair.second;
+ }
+ }
+ }
+ // return passed in functions as is.
+ return usbFunctions;
+ }
+
+ public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+ if (mDebuggingManager != null) {
+ mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
+ }
+ }
+
+ public void denyUsbDebugging() {
+ if (mDebuggingManager != null) {
+ mDebuggingManager.denyUsbDebugging();
+ }
+ }
+
+ public void clearUsbDebuggingKeys() {
+ if (mDebuggingManager != null) {
+ mDebuggingManager.clearUsbDebuggingKeys();
+ } else {
+ throw new RuntimeException("Cannot clear Usb Debugging keys, "
+ + "UsbDebuggingManager not enabled");
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw) {
+ if (mHandler != null) {
+ mHandler.dump(fd, pw);
+ }
+ if (mDebuggingManager != null) {
+ mDebuggingManager.dump(fd, pw);
+ }
+ }
+
+ private native String[] nativeGetAccessoryStrings();
+ private native ParcelFileDescriptor nativeOpenAccessory();
+ private native boolean nativeIsStartRequested();
+ private native int nativeGetAudioMode();
+}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
new file mode 100644
index 0000000..dfaad0b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -0,0 +1,222 @@
+/*
+ * 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * UsbHostManager manages USB state in host mode.
+ */
+public class UsbHostManager {
+ private static final String TAG = UsbHostManager.class.getSimpleName();
+ private static final boolean LOG = false;
+
+ // contains all connected USB devices
+ private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>();
+
+ // USB busses to exclude from USB host support
+ private final String[] mHostBlacklist;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private UsbSettingsManager mCurrentSettings;
+
+ public UsbHostManager(Context context) {
+ mContext = context;
+ mHostBlacklist = context.getResources().getStringArray(
+ com.android.internal.R.array.config_usbHostBlacklist);
+ }
+
+ public void setCurrentSettings(UsbSettingsManager settings) {
+ synchronized (mLock) {
+ mCurrentSettings = settings;
+ }
+ }
+
+ private UsbSettingsManager getCurrentSettings() {
+ synchronized (mLock) {
+ return mCurrentSettings;
+ }
+ }
+
+ private boolean isBlackListed(String deviceName) {
+ int count = mHostBlacklist.length;
+ for (int i = 0; i < count; i++) {
+ if (deviceName.startsWith(mHostBlacklist[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* returns true if the USB device should not be accessible by applications */
+ private boolean isBlackListed(int clazz, int subClass, int protocol) {
+ // blacklist hubs
+ if (clazz == UsbConstants.USB_CLASS_HUB) return true;
+
+ // blacklist HID boot devices (mouse and keyboard)
+ if (clazz == UsbConstants.USB_CLASS_HID &&
+ subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /* Called from JNI in monitorUsbHostBus() to report new USB devices */
+ private void usbDeviceAdded(String deviceName, int vendorID, int productID,
+ int deviceClass, int deviceSubclass, int deviceProtocol,
+ String manufacturerName, String productName, String serialNumber,
+ /* array of quintuples containing id, class, subclass, protocol
+ and number of endpoints for each interface */
+ int[] interfaceValues,
+ /* array of quadruples containing address, attributes, max packet size
+ and interval for each endpoint */
+ int[] endpointValues) {
+
+ if (isBlackListed(deviceName) ||
+ isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mDevices.get(deviceName) != null) {
+ Slog.w(TAG, "device already on mDevices list: " + deviceName);
+ return;
+ }
+
+ int numInterfaces = interfaceValues.length / 5;
+ Parcelable[] interfaces = new UsbInterface[numInterfaces];
+ try {
+ // repackage interfaceValues as an array of UsbInterface
+ int intf, endp, ival = 0, eval = 0;
+ for (intf = 0; intf < numInterfaces; intf++) {
+ int interfaceId = interfaceValues[ival++];
+ int interfaceClass = interfaceValues[ival++];
+ int interfaceSubclass = interfaceValues[ival++];
+ int interfaceProtocol = interfaceValues[ival++];
+ int numEndpoints = interfaceValues[ival++];
+
+ Parcelable[] endpoints = new UsbEndpoint[numEndpoints];
+ for (endp = 0; endp < numEndpoints; endp++) {
+ int address = endpointValues[eval++];
+ int attributes = endpointValues[eval++];
+ int maxPacketSize = endpointValues[eval++];
+ int interval = endpointValues[eval++];
+ endpoints[endp] = new UsbEndpoint(address, attributes,
+ maxPacketSize, interval);
+ }
+
+ // don't allow if any interfaces are blacklisted
+ if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) {
+ return;
+ }
+ interfaces[intf] = new UsbInterface(interfaceId, interfaceClass,
+ interfaceSubclass, interfaceProtocol, endpoints);
+ }
+ } catch (Exception e) {
+ // beware of index out of bound exceptions, which might happen if
+ // a device does not set bNumEndpoints correctly
+ Slog.e(TAG, "error parsing USB descriptors", e);
+ return;
+ }
+
+ UsbDevice device = new UsbDevice(deviceName, vendorID, productID,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber, interfaces);
+ mDevices.put(deviceName, device);
+ getCurrentSettings().deviceAttached(device);
+ }
+ }
+
+ /* Called from JNI in monitorUsbHostBus to report USB device removal */
+ private void usbDeviceRemoved(String deviceName) {
+ synchronized (mLock) {
+ UsbDevice device = mDevices.remove(deviceName);
+ if (device != null) {
+ getCurrentSettings().deviceDetached(device);
+ }
+ }
+ }
+
+ public void systemReady() {
+ synchronized (mLock) {
+ // Create a thread to call into native code to wait for USB host events.
+ // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
+ Runnable runnable = new Runnable() {
+ public void run() {
+ monitorUsbHostBus();
+ }
+ };
+ new Thread(null, runnable, "UsbService host thread").start();
+ }
+ }
+
+ /* Returns a list of all currently attached USB devices */
+ public void getDeviceList(Bundle devices) {
+ synchronized (mLock) {
+ for (String name : mDevices.keySet()) {
+ devices.putParcelable(name, mDevices.get(name));
+ }
+ }
+ }
+
+ /* Opens the specified USB device */
+ public ParcelFileDescriptor openDevice(String deviceName) {
+ 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");
+ }
+ getCurrentSettings().checkPermission(device);
+ return nativeOpenDevice(deviceName);
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println(" USB Host State:");
+ for (String name : mDevices.keySet()) {
+ pw.println(" " + name + ": " + mDevices.get(name));
+ }
+ }
+ }
+
+ private native void monitorUsbHostBus();
+ private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
new file mode 100644
index 0000000..b6ae192
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -0,0 +1,311 @@
+/*
+ * 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * UsbService manages all USB related state, including both host and device support.
+ * Host related events and calls are delegated to UsbHostManager, and device related
+ * support is delegated to UsbDeviceManager.
+ */
+public class UsbService extends IUsbManager.Stub {
+
+ public static class Lifecycle extends SystemService {
+ private UsbService mUsbService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mUsbService = new UsbService(getContext());
+ publishBinderService(Context.USB_SERVICE, mUsbService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mUsbService.systemReady();
+ }
+ }
+ }
+
+ private static final String TAG = "UsbService";
+
+ private final Context mContext;
+
+ private UsbDeviceManager mDeviceManager;
+ private UsbHostManager mHostManager;
+
+ private final Object mLock = new Object();
+
+ /** Map from {@link UserHandle} to {@link UsbSettingsManager} */
+ @GuardedBy("mLock")
+ private final SparseArray<UsbSettingsManager>
+ mSettingsByUser = new SparseArray<UsbSettingsManager>();
+
+ private UsbSettingsManager getSettingsForUser(int userId) {
+ synchronized (mLock) {
+ UsbSettingsManager settings = mSettingsByUser.get(userId);
+ if (settings == null) {
+ settings = new UsbSettingsManager(mContext, new UserHandle(userId));
+ mSettingsByUser.put(userId, settings);
+ }
+ return settings;
+ }
+ }
+
+ public UsbService(Context context) {
+ mContext = context;
+
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+ mHostManager = new UsbHostManager(context);
+ }
+ if (new File("/sys/class/android_usb").exists()) {
+ mDeviceManager = new UsbDeviceManager(context);
+ }
+
+ setCurrentUser(UserHandle.USER_OWNER);
+
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPED);
+ mContext.registerReceiver(mUserReceiver, userFilter, null, null);
+ }
+
+ private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ setCurrentUser(userId);
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ synchronized (mLock) {
+ mSettingsByUser.remove(userId);
+ }
+ }
+ }
+ };
+
+ private void setCurrentUser(int userId) {
+ final UsbSettingsManager userSettings = getSettingsForUser(userId);
+ if (mHostManager != null) {
+ mHostManager.setCurrentSettings(userSettings);
+ }
+ if (mDeviceManager != null) {
+ mDeviceManager.setCurrentSettings(userSettings);
+ }
+ }
+
+ public void systemReady() {
+ if (mDeviceManager != null) {
+ mDeviceManager.systemReady();
+ }
+ if (mHostManager != null) {
+ mHostManager.systemReady();
+ }
+ }
+
+ /* Returns a list of all currently attached USB devices (host mdoe) */
+ @Override
+ public void getDeviceList(Bundle devices) {
+ if (mHostManager != null) {
+ mHostManager.getDeviceList(devices);
+ }
+ }
+
+ /* Opens the specified USB device (host mode) */
+ @Override
+ public ParcelFileDescriptor openDevice(String deviceName) {
+ if (mHostManager != null) {
+ return mHostManager.openDevice(deviceName);
+ } else {
+ return null;
+ }
+ }
+
+ /* returns the currently attached USB accessory (device mode) */
+ @Override
+ public UsbAccessory getCurrentAccessory() {
+ if (mDeviceManager != null) {
+ return mDeviceManager.getCurrentAccessory();
+ } else {
+ return null;
+ }
+ }
+
+ /* opens the currently attached USB accessory (device mode) */
+ @Override
+ public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+ if (mDeviceManager != null) {
+ return mDeviceManager.openAccessory(accessory);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void setDevicePackage(UsbDevice device, String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ getSettingsForUser(userId).setDevicePackage(device, packageName);
+ }
+
+ @Override
+ public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ getSettingsForUser(userId).setAccessoryPackage(accessory, packageName);
+ }
+
+ @Override
+ public boolean hasDevicePermission(UsbDevice device) {
+ final int userId = UserHandle.getCallingUserId();
+ return getSettingsForUser(userId).hasPermission(device);
+ }
+
+ @Override
+ public boolean hasAccessoryPermission(UsbAccessory accessory) {
+ final int userId = UserHandle.getCallingUserId();
+ return getSettingsForUser(userId).hasPermission(accessory);
+ }
+
+ @Override
+ public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
+ final int userId = UserHandle.getCallingUserId();
+ getSettingsForUser(userId).requestPermission(device, packageName, pi);
+ }
+
+ @Override
+ public void requestAccessoryPermission(
+ UsbAccessory accessory, String packageName, PendingIntent pi) {
+ final int userId = UserHandle.getCallingUserId();
+ getSettingsForUser(userId).requestPermission(accessory, packageName, pi);
+ }
+
+ @Override
+ public void grantDevicePermission(UsbDevice device, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ final int userId = UserHandle.getUserId(uid);
+ getSettingsForUser(userId).grantDevicePermission(device, uid);
+ }
+
+ @Override
+ public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ final int userId = UserHandle.getUserId(uid);
+ getSettingsForUser(userId).grantAccessoryPermission(accessory, uid);
+ }
+
+ @Override
+ public boolean hasDefaults(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ return getSettingsForUser(userId).hasDefaults(packageName);
+ }
+
+ @Override
+ public void clearDefaults(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ getSettingsForUser(userId).clearDefaults(packageName);
+ }
+
+ @Override
+ public void setCurrentFunction(String function, boolean makeDefault) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ if (mDeviceManager != null) {
+ mDeviceManager.setCurrentFunctions(function, makeDefault);
+ } else {
+ throw new IllegalStateException("USB device mode not supported");
+ }
+ }
+
+ @Override
+ public void setMassStorageBackingFile(String path) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ if (mDeviceManager != null) {
+ mDeviceManager.setMassStorageBackingFile(path);
+ } else {
+ throw new IllegalStateException("USB device mode not supported");
+ }
+ }
+
+ @Override
+ public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey);
+ }
+
+ @Override
+ public void denyUsbDebugging() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ mDeviceManager.denyUsbDebugging();
+ }
+
+ @Override
+ public void clearUsbDebuggingKeys() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ mDeviceManager.clearUsbDebuggingKeys();
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
+ pw.println("USB Manager State:");
+ if (mDeviceManager != null) {
+ mDeviceManager.dump(fd, pw);
+ }
+ if (mHostManager != null) {
+ mHostManager.dump(fd, pw);
+ }
+
+ synchronized (mLock) {
+ for (int i = 0; i < mSettingsByUser.size(); i++) {
+ final int userId = mSettingsByUser.keyAt(i);
+ final UsbSettingsManager settings = mSettingsByUser.valueAt(i);
+ pw.increaseIndent();
+ pw.println("Settings for user " + userId + ":");
+ settings.dump(fd, pw);
+ pw.decreaseIndent();
+ }
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
new file mode 100644
index 0000000..ff4857b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -0,0 +1,1215 @@
+/*
+ * 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.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;
+import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+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.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;
+
+import libcore.io.IoUtils;
+
+class UsbSettingsManager {
+ private static final String TAG = "UsbSettingsManager";
+ private static final boolean DEBUG = false;
+
+ /** Legacy settings file, before multi-user */
+ private static final File sSingleUserSettingsFile = new File(
+ "/data/system/usb_device_manager.xml");
+
+ private final UserHandle mUser;
+ private final AtomicFile mSettingsFile;
+
+ private final Context mContext;
+ private final Context mUserContext;
+ private final PackageManager mPackageManager;
+
+ // Temporary mapping USB device name to list of UIDs with permissions for the device
+ private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
+ new HashMap<String, SparseBooleanArray>();
+ // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
+ private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
+ new HashMap<UsbAccessory, SparseBooleanArray>();
+ // Maps DeviceFilter to user preferred application package
+ private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
+ new HashMap<DeviceFilter, String>();
+ // 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 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;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+ }
+
+ public DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ }
+
+ public static DeviceFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if ("manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if ("product-name".equals(name)) {
+ productName = value;
+ } else if ("serial-number".equals(name)) {
+ serialNumber = value;
+ } else {
+ int intValue = -1;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+ (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber);
+ }
+
+ 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));
+ }
+ if (mManufacturerName != null) {
+ serializer.attribute(null, "manufacturer-name", mManufacturerName);
+ }
+ if (mProductName != null) {
+ serializer.attribute(null, "product-name", mProductName);
+ }
+ if (mSerialNumber != null) {
+ serializer.attribute(null, "serial-number", mSerialNumber);
+ }
+ 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;
+ if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+ if (mProductName != null && device.getProductName() == null) return false;
+ if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+ if (mManufacturerName != null && device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) return false;
+ if (mProductName != null && device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) return false;
+ if (mSerialNumber != null && device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber())) 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;
+ }
+
+ public boolean matches(DeviceFilter f) {
+ if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && f.mProductId != mProductId) return false;
+ if (f.mManufacturerName != null && mManufacturerName == null) return false;
+ if (f.mProductName != null && mProductName == null) return false;
+ if (f.mSerialNumber != null && mSerialNumber == null) return false;
+ if (mManufacturerName != null && f.mManufacturerName != null &&
+ !mManufacturerName.equals(f.mManufacturerName)) return false;
+ if (mProductName != null && f.mProductName != null &&
+ !mProductName.equals(f.mProductName)) return false;
+ if (mSerialNumber != null && f.mSerialNumber != null &&
+ !mSerialNumber.equals(f.mSerialNumber)) return false;
+
+ // check device class/subclass/protocol
+ return matches(f.mClass, f.mSubclass, f.mProtocol);
+ }
+
+ @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;
+
+ if (filter.mVendorId != mVendorId ||
+ filter.mProductId != mProductId ||
+ filter.mClass != mClass ||
+ filter.mSubclass != mSubclass ||
+ filter.mProtocol != mProtocol) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName == null) ||
+ (filter.mManufacturerName == null &&
+ mManufacturerName != null) ||
+ (filter.mProductName != null &&
+ mProductName == null) ||
+ (filter.mProductName == null &&
+ mProductName != null) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber == null) ||
+ (filter.mSerialNumber == null &&
+ mSerialNumber != null)) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName != null &&
+ !mManufacturerName.equals(filter.mManufacturerName)) ||
+ (filter.mProductName != null &&
+ mProductName != null &&
+ !mProductName.equals(filter.mProductName)) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber != null &&
+ !mSerialNumber.equals(filter.mSerialNumber))) {
+ return(false);
+ }
+ return(true);
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice)obj;
+ if (device.getVendorId() != mVendorId ||
+ device.getProductId() != mProductId ||
+ device.getDeviceClass() != mClass ||
+ device.getDeviceSubclass() != mSubclass ||
+ device.getDeviceProtocol() != mProtocol) {
+ return(false);
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+ (mManufacturerName == null && device.getManufacturerName() != null) ||
+ (mProductName != null && device.getProductName() == null) ||
+ (mProductName == null && device.getProductName() != null) ||
+ (mSerialNumber != null && device.getSerialNumber() == null) ||
+ (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return(false);
+ }
+ if ((device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) ||
+ (device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) ||
+ (device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber()))) {
+ return(false);
+ }
+ return true;
+ }
+ 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 + ",mManufacturerName=" + mManufacturerName +
+ ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+ "]";
+ }
+ }
+
+ // 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 version (or null for unspecified)
+ public final String mVersion;
+
+ public AccessoryFilter(String manufacturer, String model, String version) {
+ mManufacturer = manufacturer;
+ mModel = model;
+ mVersion = version;
+ }
+
+ public AccessoryFilter(UsbAccessory accessory) {
+ mManufacturer = accessory.getManufacturer();
+ mModel = accessory.getModel();
+ mVersion = accessory.getVersion();
+ }
+
+ public static AccessoryFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String manufacturer = null;
+ String model = 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 ("version".equals(name)) {
+ version = value;
+ }
+ }
+ return new AccessoryFilter(manufacturer, model, 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 (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 (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 || mVersion == null) {
+ return false;
+ }
+ if (obj instanceof AccessoryFilter) {
+ AccessoryFilter filter = (AccessoryFilter)obj;
+ return (mManufacturer.equals(filter.mManufacturer) &&
+ mModel.equals(filter.mModel) &&
+ mVersion.equals(filter.mVersion));
+ }
+ if (obj instanceof UsbAccessory) {
+ UsbAccessory accessory = (UsbAccessory)obj;
+ return (mManufacturer.equals(accessory.getManufacturer()) &&
+ mModel.equals(accessory.getModel()) &&
+ mVersion.equals(accessory.getVersion()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+ (mModel == null ? 0 : mModel.hashCode()) ^
+ (mVersion == null ? 0 : mVersion.hashCode()));
+ }
+
+ @Override
+ public String toString() {
+ return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+ "\", mModel=\"" + mModel +
+ "\", mVersion=\"" + mVersion + "\"]";
+ }
+ }
+
+ private class MyPackageMonitor extends PackageMonitor {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ handlePackageUpdate(packageName);
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ handlePackageUpdate(packageName);
+ return false;
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ clearDefaults(packageName);
+ }
+ }
+
+ MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+
+ public UsbSettingsManager(Context context, UserHandle user) {
+ if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
+
+ try {
+ mUserContext = context.createPackageContextAsUser("android", 0, user);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("Missing android package");
+ }
+
+ mContext = context;
+ mPackageManager = mUserContext.getPackageManager();
+
+ mUser = user;
+ mSettingsFile = new AtomicFile(new File(
+ Environment.getUserSystemDirectory(user.getIdentifier()),
+ "usb_device_manager.xml"));
+
+ synchronized (mLock) {
+ if (UserHandle.OWNER.equals(user)) {
+ upgradeSingleUserLocked();
+ }
+ readSettingsLocked();
+ }
+
+ mPackageMonitor.register(mUserContext, null, true);
+ }
+
+ 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);
+ }
+
+ /**
+ * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
+ * Should only by called by owner.
+ */
+ private void upgradeSingleUserLocked() {
+ if (sSingleUserSettingsFile.exists()) {
+ mDevicePreferenceMap.clear();
+ mAccessoryPreferenceMap.clear();
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(sSingleUserSettingsFile);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ final String tagName = parser.getName();
+ if ("preference".equals(tagName)) {
+ readPreference(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read single-user settings", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Failed to read single-user settings", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+
+ writeSettingsLocked();
+
+ // Success or failure, we delete single-user file
+ sSingleUserSettingsFile.delete();
+ }
+ }
+
+ private void readSettingsLocked() {
+ if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
+
+ mDevicePreferenceMap.clear();
+ mAccessoryPreferenceMap.clear();
+
+ FileInputStream stream = null;
+ try {
+ stream = mSettingsFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if ("preference".equals(tagName)) {
+ readPreference(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ if (DEBUG) Slog.d(TAG, "settings file not found");
+ } catch (Exception e) {
+ Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
+ mSettingsFile.delete();
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ }
+
+ private void writeSettingsLocked() {
+ if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
+
+ FileOutputStream fos = null;
+ try {
+ fos = mSettingsFile.startWrite();
+
+ FastXmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(fos, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startTag(null, "settings");
+
+ 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();
+
+ mSettingsFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write settings", e);
+ if (fos != null) {
+ mSettingsFile.failWrite(fos);
+ }
+ }
+ }
+
+ // Checks to see if a package matches a device or accessory.
+ // Only one of device and accessory should be non-null.
+ private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
+ UsbDevice device, UsbAccessory accessory) {
+ ActivityInfo ai = info.activityInfo;
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
+ if (parser == null) {
+ Slog.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) {
+ Slog.w(TAG, "Unable to load component info " + info.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return false;
+ }
+
+ private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
+ ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+ List<ResolveInfo> resolveInfos = mPackageManager.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(), device, null)) {
+ matches.add(resolveInfo);
+ }
+ }
+ return matches;
+ }
+
+ private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
+ UsbAccessory accessory, Intent intent) {
+ ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+ List<ResolveInfo> resolveInfos = mPackageManager.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(), null, accessory)) {
+ matches.add(resolveInfo);
+ }
+ }
+ return matches;
+ }
+
+ public void deviceAttached(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ ArrayList<ResolveInfo> matches;
+ String defaultPackage;
+ synchronized (mLock) {
+ matches = getDeviceMatchesLocked(device, intent);
+ // Launch our default activity directly, if we have one.
+ // Otherwise we will start the UsbResolverActivity to allow the user to choose.
+ defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
+ }
+
+ // Send broadcast to running activity with registered intent
+ mUserContext.sendBroadcast(intent);
+
+ // Start activity with registered intent
+ resolveActivity(intent, matches, defaultPackage, device, null);
+ }
+
+ public void deviceDetached(UsbDevice device) {
+ // clear temporary permissions for the device
+ mDevicePermissionMap.remove(device.getDeviceName());
+
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ public void accessoryAttached(UsbAccessory accessory) {
+ 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, 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, null, 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.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
+ String defaultPackage, UsbDevice device, UsbAccessory accessory) {
+ int count = matches.size();
+
+ // don't show the resolver activity if there are no choices available
+ 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 {
+ mUserContext.startActivityAsUser(dialogIntent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.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 (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)) {
+ defaultRI = rInfo;
+ break;
+ }
+ }
+ }
+
+ if (defaultRI != null) {
+ // grant permission for default activity
+ if (device != null) {
+ grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
+ } else if (accessory != null) {
+ grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
+ }
+
+ // start default activity directly
+ try {
+ intent.setComponent(
+ new ComponentName(defaultRI.activityInfo.packageName,
+ defaultRI.activityInfo.name));
+ mUserContext.startActivityAsUser(intent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "startActivity failed", e);
+ }
+ } else {
+ Intent resolverIntent = new Intent();
+ resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (count == 1) {
+ // start UsbConfirmActivity if there is only one choice
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbConfirmActivity");
+ resolverIntent.putExtra("rinfo", matches.get(0));
+
+ if (device != null) {
+ resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ } else {
+ resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ }
+ } else {
+ // start UsbResolverActivity so user can choose an activity
+ resolverIntent.setClassName("com.android.systemui",
+ "com.android.systemui.usb.UsbResolverActivity");
+ resolverIntent.putParcelableArrayListExtra("rlist", matches);
+ resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ }
+ try {
+ mUserContext.startActivityAsUser(resolverIntent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start activity " + resolverIntent);
+ }
+ }
+ }
+
+ private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
+ boolean changed = false;
+ for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
+ if (filter.matches(test)) {
+ mDevicePreferenceMap.remove(test);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ 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;
+ }
+
+ 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-device".equals(tagName)) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ else if ("usb-accessory".equals(tagName)) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (clearCompatibleMatchesLocked(packageName, filter)) {
+ changed = true;
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return changed;
+ }
+
+ // 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) {
+ PackageInfo info;
+ boolean changed = false;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ Slog.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_DEVICE_ATTACHED)) {
+ changed = true;
+ }
+ if (handlePackageUpdateLocked(packageName, activities[i],
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ public boolean hasPermission(UsbDevice device) {
+ synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
+ if (uidList == null) {
+ return false;
+ }
+ return uidList.get(uid);
+ }
+ }
+
+ public boolean hasPermission(UsbAccessory accessory) {
+ synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+ if (uidList == null) {
+ return false;
+ }
+ return uidList.get(uid);
+ }
+ }
+
+ public void checkPermission(UsbDevice device) {
+ if (!hasPermission(device)) {
+ throw new SecurityException("User has not given permission to device " + device);
+ }
+ }
+
+ 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) {
+ final 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(Intent.EXTRA_UID, uid);
+ try {
+ mUserContext.startActivityAsUser(intent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start UsbPermissionActivity");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ Intent intent = new Intent();
+
+ // respond immediately if permission has already been granted
+ if (hasPermission(device)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+
+ // start UsbPermissionActivity so user can choose an activity
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ requestPermissionDialog(intent, packageName, pi);
+ }
+
+ 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(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ requestPermissionDialog(intent, packageName, pi);
+ }
+
+ public void setDevicePackage(UsbDevice device, String packageName) {
+ DeviceFilter filter = new DeviceFilter(device);
+ boolean changed = false;
+ synchronized (mLock) {
+ if (packageName == null) {
+ changed = (mDevicePreferenceMap.remove(filter) != null);
+ } else {
+ changed = !packageName.equals(mDevicePreferenceMap.get(filter));
+ if (changed) {
+ mDevicePreferenceMap.put(filter, packageName);
+ }
+ }
+ if (changed) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ 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 grantDevicePermission(UsbDevice device, int uid) {
+ synchronized (mLock) {
+ String deviceName = device.getDeviceName();
+ SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+ if (uidList == null) {
+ uidList = new SparseBooleanArray(1);
+ mDevicePermissionMap.put(deviceName, uidList);
+ }
+ uidList.put(uid, true);
+ }
+ }
+
+ public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
+ synchronized (mLock) {
+ SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+ if (uidList == null) {
+ uidList = new SparseBooleanArray(1);
+ mAccessoryPermissionMap.put(accessory, uidList);
+ }
+ uidList.put(uid, true);
+ }
+ }
+
+ public boolean hasDefaults(String packageName) {
+ synchronized (mLock) {
+ if (mDevicePreferenceMap.values().contains(packageName)) return true;
+ if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
+ return false;
+ }
+ }
+
+ public void clearDefaults(String packageName) {
+ synchronized (mLock) {
+ if (clearPackageDefaultsLocked(packageName)) {
+ writeSettingsLocked();
+ }
+ }
+ }
+
+ private boolean clearPackageDefaultsLocked(String packageName) {
+ boolean cleared = false;
+ synchronized (mLock) {
+ 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) {
+ synchronized (mLock) {
+ pw.println(" Device permissions:");
+ for (String deviceName : mDevicePermissionMap.keySet()) {
+ pw.print(" " + deviceName + ": ");
+ SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+ int count = uidList.size();
+ for (int i = 0; i < count; i++) {
+ pw.print(Integer.toString(uidList.keyAt(i)) + " ");
+ }
+ pw.println("");
+ }
+ pw.println(" Accessory permissions:");
+ 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(" 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));
+ }
+ }
+ }
+}