diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-02-14 17:19:56 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2014-02-18 14:57:09 -0800 |
commit | 2cb6c60c0d2de3bc743c043aca963db6fe52662f (patch) | |
tree | ec6963bb443a13698459a1029ecd49bfc8a5d816 /services/usb/java/com | |
parent | 3d9bcb90ee6c0ffff93642539ae64fb672c7f14a (diff) | |
download | frameworks_base-2cb6c60c0d2de3bc743c043aca963db6fe52662f.zip frameworks_base-2cb6c60c0d2de3bc743c043aca963db6fe52662f.tar.gz frameworks_base-2cb6c60c0d2de3bc743c043aca963db6fe52662f.tar.bz2 |
Make UsbService optional
Tweaked the services Android.mk to build dependencies
when building with mm[m].
Change-Id: I6dad511c652bcacc085f27ede5f8f22a6982c6bd
Diffstat (limited to 'services/usb/java/com')
5 files changed, 2975 insertions, 0 deletions
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..9a4d8d8 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -0,0 +1,885 @@ +/* + * 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.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) { + int index = functions.indexOf(function); + if (index < 0) return false; + if (index > 0 && functions.charAt(index - 1) != ',') return false; + int charAfter = index + function.length(); + if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; + return true; + } + + 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); + 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); + 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)); + } + } + } +} |