diff options
author | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
commit | 628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (patch) | |
tree | 4b1c3f52d86d7fb53afbe9e9438468588fa489f8 /services/usb/java | |
parent | b11b8ec3aec8bb42f2c07e1c5ac7942da293baa8 (diff) | |
parent | d2d3a20624d968199353ccf6ddbae6f3ac39c9af (diff) |
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507
Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27
Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
Diffstat (limited to 'services/usb/java')
30 files changed, 2270 insertions, 660 deletions
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 68bd301498ad..98b9dcd2cc2f 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -41,6 +41,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.debug.AdbManagerInternal; +import android.debug.AdbNotifications; import android.debug.AdbTransportType; import android.debug.IAdbTransport; import android.hardware.usb.ParcelableUsbPort; @@ -162,6 +163,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static final int MSG_GET_CURRENT_USB_FUNCTIONS = 16; private static final int MSG_FUNCTION_SWITCH_TIMEOUT = 17; private static final int MSG_GADGET_HAL_REGISTERED = 18; + private static final int MSG_RESET_USB_GADGET = 19; private static final int AUDIO_MODE_SOURCE = 1; @@ -251,7 +253,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } public UsbDeviceManager(Context context, UsbAlsaManager alsaManager, - UsbSettingsManager settingsManager) { + UsbSettingsManager settingsManager, UsbPermissionManager permissionManager) { mContext = context; mContentResolver = context.getContentResolver(); PackageManager pm = mContext.getPackageManager(); @@ -285,13 +287,13 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser * Initialze the legacy UsbHandler */ mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this, - alsaManager, settingsManager); + alsaManager, permissionManager); } else { /** * Initialize HAL based UsbHandler */ mHandler = new UsbHandlerHal(FgThread.get().getLooper(), mContext, this, - alsaManager, settingsManager); + alsaManager, permissionManager); } if (nativeIsStartRequested()) { @@ -444,7 +446,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser abstract static class UsbHandler extends Handler { // current USB state - private boolean mConnected; private boolean mHostConnected; private boolean mSourcePower; private boolean mSinkPower; @@ -469,9 +470,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private final Context mContext; private final UsbAlsaManager mUsbAlsaManager; - private final UsbSettingsManager mSettingsManager; + private final UsbPermissionManager mPermissionManager; private NotificationManager mNotificationManager; + protected boolean mConnected; protected long mScreenUnlockedFunctions; protected boolean mBootCompleted; protected boolean mCurrentFunctionsApplied; @@ -490,12 +492,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config"; UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager, - UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { + UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { super(looper); mContext = context; mUsbDeviceManager = deviceManager; mUsbAlsaManager = alsaManager; - mSettingsManager = settingsManager; + mPermissionManager = permissionManager; mContentResolver = context.getContentResolver(); mCurrentUser = ActivityManager.getCurrentUser(); @@ -626,7 +628,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // successfully entered accessory mode String[] accessoryStrings = mUsbDeviceManager.getAccessoryStrings(); if (accessoryStrings != null) { - UsbSerialReader serialReader = new UsbSerialReader(mContext, mSettingsManager, + UsbSerialReader serialReader = new UsbSerialReader(mContext, mPermissionManager, accessoryStrings[UsbAccessory.SERIAL_STRING]); mCurrentAccessory = new UsbAccessory( @@ -664,7 +666,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (mCurrentAccessory != null) { if (mBootCompleted) { - mSettingsManager.usbAccessoryRemoved(mCurrentAccessory); + mPermissionManager.usbAccessoryRemoved(mCurrentAccessory); } mCurrentAccessory = null; } @@ -1179,7 +1181,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser protected void updateAdbNotification(boolean force) { if (mNotificationManager == null) return; final int id = SystemMessage.NOTE_ADB_ACTIVE; - final int titleRes = com.android.internal.R.string.adb_active_notification_title; if (isAdbEnabled() && mConnected) { if ("0".equals(getSystemProperty("persist.adb.notify", ""))) return; @@ -1190,37 +1191,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } if (!mAdbNotificationShown) { - Resources r = mContext.getResources(); - CharSequence title = r.getText(titleRes); - CharSequence message = r.getText( - com.android.internal.R.string.adb_active_notification_message); - - Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK); - PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, - intent, 0, null, UserHandle.CURRENT); - - Notification notification = - new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) - .setWhen(0) - .setOngoing(true) - .setTicker(title) - .setDefaults(0) // please be quiet - .setColor(mContext.getColor( - com.android.internal.R.color - .system_notification_accent_color)) - .setContentTitle(title) - .setContentText(message) - .setContentIntent(pi) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .extend(new Notification.TvExtender() - .setChannelId(ADB_NOTIFICATION_CHANNEL_ID_TV)) - .build(); + Notification notification = AdbNotifications.createNotification(mContext, + AdbTransportType.USB); mAdbNotificationShown = true; - mNotificationManager.notifyAsUser(null, id, notification, - UserHandle.ALL); + mNotificationManager.notifyAsUser(null, id, notification, UserHandle.ALL); } } else if (mAdbNotificationShown) { mAdbNotificationShown = false; @@ -1346,8 +1320,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private boolean mUsbDataUnlocked; UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager, - UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { - super(looper, context, deviceManager, alsaManager, settingsManager); + UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { + super(looper, context, deviceManager, alsaManager, permissionManager); try { readOemUsbOverrideConfig(context); // Restore default functions. @@ -1726,8 +1700,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser protected boolean mCurrentUsbFunctionsRequested; UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager, - UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { - super(looper, context, deviceManager, alsaManager, settingsManager); + UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { + super(looper, context, deviceManager, alsaManager, permissionManager); try { ServiceNotification serviceNotification = new ServiceNotification(); @@ -1792,7 +1766,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser case MSG_SET_FUNCTIONS_TIMEOUT: Slog.e(TAG, "Set functions timed out! no reply from usb hal"); if (msg.arg1 != 1) { - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + // Set this since default function may be selected from Developer options + setEnabledFunctions(mScreenUnlockedFunctions, false); } break; case MSG_GET_CURRENT_USB_FUNCTIONS: @@ -1814,7 +1789,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser * Dont force to default when the configuration is already set to default. */ if (msg.arg1 != 1) { - setEnabledFunctions(UsbManager.FUNCTION_NONE, !isAdbEnabled()); + // Set this since default function may be selected from Developer options + setEnabledFunctions(mScreenUnlockedFunctions, false); } break; case MSG_GADGET_HAL_REGISTERED: @@ -1834,6 +1810,23 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } break; + case MSG_RESET_USB_GADGET: + synchronized (mGadgetProxyLock) { + if (mGadgetProxy == null) { + Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null"); + break; + } + + try { + android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy = + android.hardware.usb.gadget.V1_1.IUsbGadget + .castFrom(mGadgetProxy); + gadgetProxy.reset(); + } catch (RemoteException e) { + Slog.e(TAG, "reset Usb Gadget failed", e); + } + } + break; default: super.handleMessage(msg); } @@ -1917,8 +1910,11 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS); sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions, SET_FUNCTIONS_TIMEOUT_MS); - sendMessageDelayed(MSG_FUNCTION_SWITCH_TIMEOUT, chargingFunctions, - SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS); + if (mConnected) { + // Only queue timeout of enumeration when the USB is connected + sendMessageDelayed(MSG_FUNCTION_SWITCH_TIMEOUT, chargingFunctions, + SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS); + } if (DEBUG) Slog.d(TAG, "timeout message queued"); } catch (RemoteException e) { Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e); @@ -1967,7 +1963,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser * @param uid Uid of the caller */ public ParcelFileDescriptor openAccessory(UsbAccessory accessory, - UsbUserSettingsManager settings, int uid) { + UsbUserPermissionManager permissions, int uid) { UsbAccessory currentAccessory = mHandler.getCurrentAccessory(); if (currentAccessory == null) { throw new IllegalArgumentException("no accessory attached"); @@ -1978,7 +1974,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser + currentAccessory; throw new IllegalArgumentException(error); } - settings.checkPermission(accessory, uid); + permissions.checkPermission(accessory, uid); return nativeOpenAccessory(); } @@ -2044,6 +2040,17 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions); } + /** + * Resets the USB Gadget. + */ + public void resetUsbGadget() { + if (DEBUG) { + Slog.d(TAG, "reset Usb Gadget"); + } + + mHandler.sendMessage(MSG_RESET_USB_GADGET, null); + } + private void onAdbEnabled(boolean enabled) { mHandler.sendMessage(MSG_ENABLE_ADB, enabled); } diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java index 1730d8f22950..f3112743bcf2 100644 --- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java +++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java @@ -19,6 +19,7 @@ package com.android.server.usb; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ActivityNotFoundException; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; @@ -59,8 +60,9 @@ class UsbHandlerManager { if (uri != null && uri.length() > 0) { // display URI to user Intent dialogIntent = createDialogIntent(); - dialogIntent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbAccessoryUriActivity"); + dialogIntent.setComponent(ComponentName.unflattenFromString( + mContext.getResources().getString( + com.android.internal.R.string.config_usbAccessoryUriActivity))); dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); dialogIntent.putExtra("uri", uri); try { @@ -84,8 +86,9 @@ class UsbHandlerManager { @Nullable UsbAccessory accessory) { Intent resolverIntent = createDialogIntent(); // start UsbConfirmActivity if there is only one choice - resolverIntent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbConfirmActivity"); + resolverIntent.setComponent(ComponentName.unflattenFromString( + mContext.getResources().getString( + com.android.internal.R.string.config_usbConfirmActivity))); resolverIntent.putExtra("rinfo", rInfo); UserHandle user = UserHandle.getUserHandleForUid(rInfo.activityInfo.applicationInfo.uid); @@ -115,8 +118,9 @@ class UsbHandlerManager { void selectUsbHandler(@NonNull ArrayList<ResolveInfo> matches, @NonNull UserHandle user, @NonNull Intent intent) { Intent resolverIntent = createDialogIntent(); - resolverIntent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbResolverActivity"); + resolverIntent.setComponent(ComponentName.unflattenFromString( + mContext.getResources().getString( + com.android.internal.R.string.config_usbResolverActivity))); resolverIntent.putParcelableArrayListExtra("rlist", matches); resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index c0097bfc6f81..f33001c9241e 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -34,9 +34,9 @@ import android.service.usb.UsbIsHeadsetProto; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.usb.descriptors.UsbDescriptor; @@ -65,7 +65,7 @@ public class UsbHostManager { private final String[] mHostDenyList; private final UsbAlsaManager mUsbAlsaManager; - private final UsbSettingsManager mSettingsManager; + private final UsbPermissionManager mPermissionManager; private final Object mLock = new Object(); @GuardedBy("mLock") @@ -232,13 +232,13 @@ public class UsbHostManager { * UsbHostManager */ public UsbHostManager(Context context, UsbAlsaManager alsaManager, - UsbSettingsManager settingsManager) { + UsbPermissionManager permissionManager) { mContext = context; mHostDenyList = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostDenylist); mUsbAlsaManager = alsaManager; - mSettingsManager = settingsManager; + mPermissionManager = permissionManager; String deviceConnectionHandler = context.getResources().getString( com.android.internal.R.string.config_UsbDeviceConnectionHandling_component); if (!TextUtils.isEmpty(deviceConnectionHandler)) { @@ -386,15 +386,15 @@ public class UsbHostManager { return false; } - UsbDevice.Builder newDeviceBuilder = parser.toAndroidUsbDevice(); + UsbDevice.Builder newDeviceBuilder = parser.toAndroidUsbDeviceBuilder(); if (newDeviceBuilder == null) { Slog.e(TAG, "Couldn't create UsbDevice object."); // Tracking addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE, parser.getRawDescriptors()); } else { - UsbSerialReader serialNumberReader = new UsbSerialReader(mContext, mSettingsManager, - newDeviceBuilder.serialNumber); + UsbSerialReader serialNumberReader = new UsbSerialReader(mContext, + mPermissionManager, newDeviceBuilder.serialNumber); UsbDevice newDevice = newDeviceBuilder.build(serialNumberReader); serialNumberReader.setDevice(newDevice); @@ -418,10 +418,11 @@ public class UsbHostManager { parser.getRawDescriptors()); // Stats collection - StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, newDevice.getVendorId(), - newDevice.getProductId(), parser.hasAudioInterface(), - parser.hasHIDInterface(), parser.hasStorageInterface(), - StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_CONNECTED, 0); + FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED, + newDevice.getVendorId(), newDevice.getProductId(), + parser.hasAudioInterface(), parser.hasHIDInterface(), + parser.hasStorageInterface(), + FrameworkStatsLog.USB_DEVICE_ATTACHED__STATE__STATE_CONNECTED, 0); } } @@ -444,7 +445,7 @@ public class UsbHostManager { if (device != null) { Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName()); mUsbAlsaManager.usbDeviceRemoved(deviceAddress); - mSettingsManager.usbDeviceRemoved(device); + mPermissionManager.usbDeviceRemoved(device); getCurrentUserSettings().usbDeviceRemoved(device); ConnectionRecord current = mConnected.get(deviceAddress); // Tracking @@ -454,10 +455,10 @@ public class UsbHostManager { UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, current.mDescriptors); // Stats collection - StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, device.getVendorId(), - device.getProductId(), parser.hasAudioInterface(), + FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED, + device.getVendorId(), device.getProductId(), parser.hasAudioInterface(), parser.hasHIDInterface(), parser.hasStorageInterface(), - StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_DISCONNECTED, + FrameworkStatsLog.USB_DEVICE_ATTACHED__STATE__STATE_DISCONNECTED, System.currentTimeMillis() - current.mTimestamp); } } else { @@ -484,9 +485,11 @@ public class UsbHostManager { } } - /* Opens the specified USB device */ - public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings, - String packageName, int pid, int uid) { + /** + * Opens the specified USB device + */ + public ParcelFileDescriptor openDevice(String deviceAddress, + UsbUserPermissionManager permissions, String packageName, int pid, int uid) { synchronized (mLock) { if (isDenyListed(deviceAddress)) { throw new SecurityException("USB device is on a restricted bus"); @@ -498,7 +501,7 @@ public class UsbHostManager { "device " + deviceAddress + " does not exist or is restricted"); } - settings.checkPermission(device, packageName, pid, uid); + permissions.checkPermission(device, packageName, pid, uid); return nativeOpenDevice(deviceAddress); } } diff --git a/services/usb/java/com/android/server/usb/UsbPermissionManager.java b/services/usb/java/com/android/server/usb/UsbPermissionManager.java index dd2f29ba74ae..5d3ed4fc6acf 100644 --- a/services/usb/java/com/android/server/usb/UsbPermissionManager.java +++ b/services/usb/java/com/android/server/usb/UsbPermissionManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 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. @@ -17,230 +17,119 @@ package com.android.server.usb; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.content.ActivityNotFoundException; +import android.annotation.UserIdInt; import android.content.Context; import android.content.Intent; +import android.content.pm.UserInfo; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; -import android.os.Binder; -import android.os.Process; import android.os.UserHandle; -import android.service.usb.UsbSettingsAccessoryPermissionProto; -import android.service.usb.UsbSettingsDevicePermissionProto; -import android.service.usb.UsbUserSettingsManagerProto; +import android.os.UserManager; +import android.service.usb.UsbSettingsManagerProto; import android.util.Slog; -import android.util.SparseBooleanArray; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.dump.DualDumpOutputStream; -import java.util.HashMap; +import java.util.List; -/** - * UsbPermissionManager manages usb device or accessory access permissions. - * - * @hide - */ class UsbPermissionManager { private static final String LOG_TAG = UsbPermissionManager.class.getSimpleName(); + private static final boolean DEBUG = false; - @GuardedBy("mLock") - /** Temporary mapping USB device name to list of UIDs with permissions for the device*/ - private final HashMap<String, SparseBooleanArray> mDevicePermissionMap = - new HashMap<>(); - @GuardedBy("mLock") - /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory*/ - private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = - new HashMap<>(); + /** Context to be used by this module */ + private final @NonNull Context mContext; - private final UserHandle mUser; - private final boolean mDisablePermissionDialogs; + /** Map from user id to {@link UsbUserPermissionManager} for the user */ + @GuardedBy("mPermissionsByUser") + private final SparseArray<UsbUserPermissionManager> mPermissionsByUser = new SparseArray<>(); - private final Object mLock = new Object(); + final UsbService mUsbService; - UsbPermissionManager(@NonNull Context context, @NonNull UserHandle user) { - mUser = user; - mDisablePermissionDialogs = context.getResources().getBoolean( - com.android.internal.R.bool.config_disableUsbPermissionDialogs); + UsbPermissionManager(@NonNull Context context, + @NonNull UsbService usbService) { + mContext = context; + mUsbService = usbService; } - /** - * Removes access permissions of all packages for the USB accessory. - * - * @param accessory to remove permissions for - */ - void removeAccessoryPermissions(@NonNull UsbAccessory accessory) { - synchronized (mLock) { - mAccessoryPermissionMap.remove(accessory); + @NonNull UsbUserPermissionManager getPermissionsForUser(@UserIdInt int userId) { + synchronized (mPermissionsByUser) { + UsbUserPermissionManager permissions = mPermissionsByUser.get(userId); + if (permissions == null) { + permissions = new UsbUserPermissionManager(mContext.createContextAsUser( + UserHandle.of(userId), 0), mUsbService.getSettingsForUser(userId)); + mPermissionsByUser.put(userId, permissions); + } + return permissions; } } - /** - * Removes access permissions of all packages for the USB device. - * - * @param device to remove permissions for - */ - void removeDevicePermissions(@NonNull UsbDevice device) { - synchronized (mLock) { - mDevicePermissionMap.remove(device.getDeviceName()); - } + @NonNull UsbUserPermissionManager getPermissionsForUser(@NonNull UserHandle user) { + return getPermissionsForUser(user.getIdentifier()); } - /** - * Grants permission for USB device without showing system dialog for package with uid. - * - * @param device to grant permission for - * @param uid to grant permission for - */ - void grantDevicePermission(@NonNull 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); + void remove(@NonNull UserHandle userToRemove) { + synchronized (mPermissionsByUser) { + mPermissionsByUser.remove(userToRemove.getIdentifier()); } } /** - * Grants permission for USB accessory without showing system dialog for package with uid. + * Remove temporary access permission and broadcast that a device was removed. * - * @param accessory to grant permission for - * @param uid to grant permission for + * @param device The device that is removed */ - void grantAccessoryPermission(@NonNull UsbAccessory accessory, int uid) { - synchronized (mLock) { - SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); - if (uidList == null) { - uidList = new SparseBooleanArray(1); - mAccessoryPermissionMap.put(accessory, uidList); + void usbDeviceRemoved(@NonNull UsbDevice device) { + synchronized (mPermissionsByUser) { + for (int i = 0; i < mPermissionsByUser.size(); i++) { + // clear temporary permissions for the device + mPermissionsByUser.valueAt(i).removeDevicePermissions(device); } - uidList.put(uid, true); } - } - /** - * Returns true if package with uid has permission to access the device. - * - * @param device to check permission for - * @param uid to check permission for - * @return {@code true} if package with uid has permission - */ - boolean hasPermission(@NonNull UsbDevice device, int uid) { - synchronized (mLock) { - if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { - return true; - } - SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName()); - if (uidList == null) { - return false; - } - return uidList.get(uid); + Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + + if (DEBUG) { + Slog.d(LOG_TAG, "usbDeviceRemoved, sending " + intent); } + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } /** - * Returns true if caller has permission to access the accessory. + * Remove temporary access permission and broadcast that a accessory was removed. * - * @param accessory to check permission for - * @param uid to check permission for - * @return {@code true} if caller has permssion + * @param accessory The accessory that is removed */ - boolean hasPermission(@NonNull UsbAccessory accessory, int uid) { - synchronized (mLock) { - if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { - return true; - } - SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); - if (uidList == null) { - return false; + void usbAccessoryRemoved(@NonNull UsbAccessory accessory) { + synchronized (mPermissionsByUser) { + for (int i = 0; i < mPermissionsByUser.size(); i++) { + // clear temporary permissions for the accessory + mPermissionsByUser.valueAt(i).removeAccessoryPermissions(accessory); } - return uidList.get(uid); } - } - /** - * Creates UI dialog to request permission for the given package to access the device - * or accessory. - * - * @param device The USB device attached - * @param accessory The USB accessory attached - * @param canBeDefault Whether the calling pacakge can set as default handler - * of the USB device or accessory - * @param packageName The package name of the calling package - * @param uid The uid of the calling package - * @param userContext The context to start the UI dialog - * @param pi PendingIntent for returning result - */ - void requestPermissionDialog(@Nullable UsbDevice device, - @Nullable UsbAccessory accessory, - boolean canBeDefault, - @NonNull String packageName, - int uid, - @NonNull Context userContext, - @NonNull PendingIntent pi) { - long identity = Binder.clearCallingIdentity(); - Intent intent = new Intent(); - if (device != null) { - intent.putExtra(UsbManager.EXTRA_DEVICE, device); - } else { - intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - } - intent.putExtra(Intent.EXTRA_INTENT, pi); - intent.putExtra(Intent.EXTRA_UID, uid); - intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault); - intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName); - intent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbPermissionActivity"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - try { - userContext.startActivityAsUser(intent, mUser); - } catch (ActivityNotFoundException e) { - Slog.e(LOG_TAG, "unable to start UsbPermissionActivity"); - } finally { - Binder.restoreCallingIdentity(identity); - } + Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_DETACHED); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } - void dump(@NonNull DualDumpOutputStream dump) { - synchronized (mLock) { - for (String deviceName : mDevicePermissionMap.keySet()) { - long devicePermissionToken = dump.start("device_permissions", - UsbUserSettingsManagerProto.DEVICE_PERMISSIONS); - - dump.write("device_name", UsbSettingsDevicePermissionProto.DEVICE_NAME, deviceName); - - SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName); - int count = uidList.size(); - for (int i = 0; i < count; i++) { - dump.write("uids", UsbSettingsDevicePermissionProto.UIDS, uidList.keyAt(i)); - } - - dump.end(devicePermissionToken); - } - - for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) { - long accessoryPermissionToken = dump.start("accessory_permissions", - UsbUserSettingsManagerProto.ACCESSORY_PERMISSIONS); - - dump.write("accessory_description", - UsbSettingsAccessoryPermissionProto.ACCESSORY_DESCRIPTION, - accessory.getDescription()); - - SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); - int count = uidList.size(); - for (int i = 0; i < count; i++) { - dump.write("uids", UsbSettingsAccessoryPermissionProto.UIDS, uidList.keyAt(i)); - } - - dump.end(accessoryPermissionToken); + void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { + long token = dump.start(idName, id); + UserManager userManager = mContext.getSystemService(UserManager.class); + synchronized (mPermissionsByUser) { + List<UserInfo> users = userManager.getUsers(); + int numUsers = users.size(); + for (int i = 0; i < numUsers; i++) { + getPermissionsForUser(users.get(i).id).dump(dump, "user_permissions", + UsbSettingsManagerProto.USER_SETTINGS); } } + dump.end(token); } + } diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 0e30f9380ff3..ec7d4bd0d8c0 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -34,6 +34,7 @@ import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -65,11 +66,11 @@ import android.service.usb.UsbServiceProto; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; @@ -225,8 +226,8 @@ public class UsbPortManager { Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setClassName("com.android.systemui", - "com.android.systemui.usb.UsbContaminantActivity"); + intent.setComponent(ComponentName.unflattenFromString(r.getString( + com.android.internal.R.string.config_usbContaminantActivity))); intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort)); PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, @@ -1039,8 +1040,9 @@ public class UsbPortManager { if (mConnected.containsKey(portInfo.mUsbPort.getId())) { //Previous logged a connected. Set it to disconnected. if (mConnected.get(portInfo.mUsbPort.getId())) { - StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED, - StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, + FrameworkStatsLog.write(FrameworkStatsLog.USB_CONNECTOR_STATE_CHANGED, + FrameworkStatsLog + .USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); } mConnected.remove(portInfo.mUsbPort.getId()); @@ -1050,7 +1052,7 @@ public class UsbPortManager { //Previous logged a contaminant detected. Set it to not detected. if ((mContaminantStatus.get(portInfo.mUsbPort.getId()) == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) { - StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + FrameworkStatsLog.write(FrameworkStatsLog.USB_CONTAMINANT_REPORTED, portInfo.mUsbPort.getId(), convertContaminantDetectionStatusToProto( UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED)); @@ -1064,10 +1066,10 @@ public class UsbPortManager { || (mConnected.get(portInfo.mUsbPort.getId()) != portInfo.mUsbPortStatus.isConnected())) { mConnected.put(portInfo.mUsbPort.getId(), portInfo.mUsbPortStatus.isConnected()); - StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED, + FrameworkStatsLog.write(FrameworkStatsLog.USB_CONNECTOR_STATE_CHANGED, portInfo.mUsbPortStatus.isConnected() - ? StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_CONNECTED : - StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, + ? FrameworkStatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_CONNECTED : + FrameworkStatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); } @@ -1076,7 +1078,7 @@ public class UsbPortManager { != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) { mContaminantStatus.put(portInfo.mUsbPort.getId(), portInfo.mUsbPortStatus.getContaminantDetectionStatus()); - StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED, + FrameworkStatsLog.write(FrameworkStatsLog.USB_CONTAMINANT_REPORTED, portInfo.mUsbPort.getId(), convertContaminantDetectionStatusToProto( portInfo.mUsbPortStatus.getContaminantDetectionStatus())); diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index 74c3939a1b1c..d7b6b5d0d36a 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -46,6 +46,8 @@ import android.service.usb.UsbProfileGroupSettingsManagerProto; import android.service.usb.UsbSettingsAccessoryPreferenceProto; import android.service.usb.UsbSettingsDevicePreferenceProto; import android.service.usb.UserPackageProto; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; import android.util.Slog; @@ -70,6 +72,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -102,10 +105,20 @@ class UsbProfileGroupSettingsManager { @GuardedBy("mLock") private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>(); + /** Maps DeviceFilter to set of UserPackages not to ask for launch preference anymore */ + @GuardedBy("mLock") + private final ArrayMap<DeviceFilter, ArraySet<UserPackage>> mDevicePreferenceDeniedMap = + new ArrayMap<>(); + /** Maps AccessoryFilter to user preferred application package */ @GuardedBy("mLock") private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>(); + /** Maps AccessoryFilter to set of UserPackages not to ask for launch preference anymore */ + @GuardedBy("mLock") + private final ArrayMap<AccessoryFilter, ArraySet<UserPackage>> mAccessoryPreferenceDeniedMap = + new ArrayMap<>(); + private final Object mLock = new Object(); /** @@ -248,11 +261,11 @@ class UsbProfileGroupSettingsManager { } /** - * Remove all defaults for a user. + * Remove all defaults and denied packages for a user. * - * @param userToRemove The user the defaults belong to. + * @param userToRemove The user */ - void removeAllDefaultsForUser(@NonNull UserHandle userToRemove) { + void removeUser(@NonNull UserHandle userToRemove) { synchronized (mLock) { boolean needToPersist = false; Iterator<Map.Entry<DeviceFilter, UserPackage>> devicePreferenceIt = mDevicePreferenceMap @@ -277,6 +290,28 @@ class UsbProfileGroupSettingsManager { } } + int numEntries = mDevicePreferenceDeniedMap.size(); + for (int i = 0; i < numEntries; i++) { + ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.valueAt(i); + for (int j = userPackages.size() - 1; j >= 0; j--) { + if (userPackages.valueAt(j).user.equals(userToRemove)) { + userPackages.removeAt(j); + needToPersist = true; + } + } + } + + numEntries = mAccessoryPreferenceDeniedMap.size(); + for (int i = 0; i < numEntries; i++) { + ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.valueAt(i); + for (int j = userPackages.size() - 1; j >= 0; j--) { + if (userPackages.valueAt(j).user.equals(userToRemove)) { + userPackages.removeAt(j); + needToPersist = true; + } + } + } + if (needToPersist) { scheduleWriteSettingsLocked(); } @@ -284,7 +319,7 @@ class UsbProfileGroupSettingsManager { } private void readPreference(XmlPullParser parser) - throws XmlPullParserException, IOException { + throws IOException, XmlPullParserException { String packageName = null; // If not set, assume it to be the parent profile @@ -317,9 +352,70 @@ class UsbProfileGroupSettingsManager { XmlUtils.nextElement(parser); } + private void readPreferenceDeniedList(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + if (!XmlUtils.nextElementWithin(parser, outerDepth)) { + return; + } + + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if ("user-package".equals(parser.getName())) { + try { + int userId = XmlUtils.readIntAttribute(parser, "user"); + + String packageName = XmlUtils.readStringAttribute(parser, "package"); + if (packageName == null) { + Slog.e(TAG, "Unable to parse package name"); + } + + ArraySet<UserPackage> set = mDevicePreferenceDeniedMap.get(filter); + if (set == null) { + set = new ArraySet<>(); + mDevicePreferenceDeniedMap.put(filter, set); + } + set.add(new UserPackage(packageName, UserHandle.of(userId))); + } catch (ProtocolException e) { + Slog.e(TAG, "Unable to parse user id", e); + } + } + } + } else if ("usb-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if ("user-package".equals(parser.getName())) { + try { + int userId = XmlUtils.readIntAttribute(parser, "user"); + + String packageName = XmlUtils.readStringAttribute(parser, "package"); + if (packageName == null) { + Slog.e(TAG, "Unable to parse package name"); + } + + ArraySet<UserPackage> set = mAccessoryPreferenceDeniedMap.get(filter); + if (set == null) { + set = new ArraySet<>(); + mAccessoryPreferenceDeniedMap.put(filter, set); + } + set.add(new UserPackage(packageName, UserHandle.of(userId))); + } catch (ProtocolException e) { + Slog.e(TAG, "Unable to parse user id", e); + } + } + } + } + + while (parser.getDepth() > outerDepth) { + parser.nextTag(); // ignore unknown tags + } + } + /** * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}. - * Should only by called by owner. + * Should only be called by owner. */ @GuardedBy("mLock") private void upgradeSingleUserLocked() { @@ -373,6 +469,8 @@ class UsbProfileGroupSettingsManager { String tagName = parser.getName(); if ("preference".equals(tagName)) { readPreference(parser); + } else if ("preference-denied-list".equals(tagName)) { + readPreferenceDeniedList(parser); } else { XmlUtils.nextElement(parser); } @@ -436,6 +534,46 @@ class UsbProfileGroupSettingsManager { serializer.endTag(null, "preference"); } + int numEntries = mDevicePreferenceDeniedMap.size(); + for (int i = 0; i < numEntries; i++) { + DeviceFilter filter = mDevicePreferenceDeniedMap.keyAt(i); + ArraySet<UserPackage> userPackageSet = mDevicePreferenceDeniedMap + .valueAt(i); + serializer.startTag(null, "preference-denied-list"); + filter.write(serializer); + + int numUserPackages = userPackageSet.size(); + for (int j = 0; j < numUserPackages; j++) { + UserPackage userPackage = userPackageSet.valueAt(j); + serializer.startTag(null, "user-package"); + serializer.attribute(null, "user", + String.valueOf(getSerial(userPackage.user))); + serializer.attribute(null, "package", userPackage.packageName); + serializer.endTag(null, "user-package"); + } + serializer.endTag(null, "preference-denied-list"); + } + + numEntries = mAccessoryPreferenceDeniedMap.size(); + for (int i = 0; i < numEntries; i++) { + AccessoryFilter filter = mAccessoryPreferenceDeniedMap.keyAt(i); + ArraySet<UserPackage> userPackageSet = + mAccessoryPreferenceDeniedMap.valueAt(i); + serializer.startTag(null, "preference-denied-list"); + filter.write(serializer); + + int numUserPackages = userPackageSet.size(); + for (int j = 0; j < numUserPackages; j++) { + UserPackage userPackage = userPackageSet.valueAt(j); + serializer.startTag(null, "user-package"); + serializer.attribute(null, "user", + String.valueOf(getSerial(userPackage.user))); + serializer.attribute(null, "package", userPackage.packageName); + serializer.endTag(null, "user-package"); + } + serializer.endTag(null, "preference-denied-list"); + } + serializer.endTag(null, "settings"); serializer.endDocument(); @@ -783,7 +921,7 @@ class UsbProfileGroupSettingsManager { return; } - mSettingsManager.getSettingsForUser(UserHandle.getUserId(appInfo.uid)) + mSettingsManager.mUsbService.getPermissionsForUser(UserHandle.getUserId(appInfo.uid)) .grantDevicePermission(device, appInfo.uid); Intent activityIntent = new Intent(intent); @@ -834,6 +972,25 @@ class UsbProfileGroupSettingsManager { private void resolveActivity(@NonNull Intent intent, @NonNull ArrayList<ResolveInfo> matches, @Nullable ActivityInfo defaultActivity, @Nullable UsbDevice device, @Nullable UsbAccessory accessory) { + // Remove all matches which are on the denied list + ArraySet deniedPackages = null; + if (device != null) { + deniedPackages = mDevicePreferenceDeniedMap.get(new DeviceFilter(device)); + } else if (accessory != null) { + deniedPackages = mAccessoryPreferenceDeniedMap.get(new AccessoryFilter(accessory)); + } + if (deniedPackages != null) { + for (int i = matches.size() - 1; i >= 0; i--) { + ResolveInfo match = matches.get(i); + String packageName = match.activityInfo.packageName; + UserHandle user = UserHandle + .getUserHandleForUid(match.activityInfo.applicationInfo.uid); + if (deniedPackages.contains(new UserPackage(packageName, user))) { + matches.remove(i); + } + } + } + // don't show the resolver activity if there are no choices available if (matches.size() == 0) { if (accessory != null) { @@ -844,14 +1001,15 @@ class UsbProfileGroupSettingsManager { } if (defaultActivity != null) { - UsbUserSettingsManager defaultRIUserSettings = mSettingsManager.getSettingsForUser( - UserHandle.getUserId(defaultActivity.applicationInfo.uid)); + UsbUserPermissionManager defaultRIUserPermissions = + mSettingsManager.mUsbService.getPermissionsForUser( + UserHandle.getUserId(defaultActivity.applicationInfo.uid)); // grant permission for default activity if (device != null) { - defaultRIUserSettings. - grantDevicePermission(device, defaultActivity.applicationInfo.uid); + defaultRIUserPermissions + .grantDevicePermission(device, defaultActivity.applicationInfo.uid); } else if (accessory != null) { - defaultRIUserSettings.grantAccessoryPermission(accessory, + defaultRIUserPermissions.grantAccessoryPermission(accessory, defaultActivity.applicationInfo.uid); } @@ -1075,6 +1233,156 @@ class UsbProfileGroupSettingsManager { } /** + * Add package to the denied for handling a device + * + * @param device the device to add to the denied + * @param packageNames the packages to not become handler + * @param user the user + */ + void addDevicePackagesToDenied(@NonNull UsbDevice device, @NonNull String[] packageNames, + @NonNull UserHandle user) { + if (packageNames.length == 0) { + return; + } + DeviceFilter filter = new DeviceFilter(device); + + synchronized (mLock) { + ArraySet<UserPackage> userPackages; + if (mDevicePreferenceDeniedMap.containsKey(filter)) { + userPackages = mDevicePreferenceDeniedMap.get(filter); + } else { + userPackages = new ArraySet<>(); + mDevicePreferenceDeniedMap.put(filter, userPackages); + } + + boolean shouldWrite = false; + for (String packageName : packageNames) { + UserPackage userPackage = new UserPackage(packageName, user); + if (!userPackages.contains(userPackage)) { + userPackages.add(userPackage); + shouldWrite = true; + } + } + + if (shouldWrite) { + scheduleWriteSettingsLocked(); + } + } + } + + /** + * Add package to the denied for handling a accessory + * + * @param accessory the accessory to add to the denied + * @param packageNames the packages to not become handler + * @param user the user + */ + void addAccessoryPackagesToDenied(@NonNull UsbAccessory accessory, + @NonNull String[] packageNames, @NonNull UserHandle user) { + if (packageNames.length == 0) { + return; + } + AccessoryFilter filter = new AccessoryFilter(accessory); + + synchronized (mLock) { + ArraySet<UserPackage> userPackages; + if (mAccessoryPreferenceDeniedMap.containsKey(filter)) { + userPackages = mAccessoryPreferenceDeniedMap.get(filter); + } else { + userPackages = new ArraySet<>(); + mAccessoryPreferenceDeniedMap.put(filter, userPackages); + } + + boolean shouldWrite = false; + for (String packageName : packageNames) { + UserPackage userPackage = new UserPackage(packageName, user); + if (!userPackages.contains(userPackage)) { + userPackages.add(userPackage); + shouldWrite = true; + } + } + + if (shouldWrite) { + scheduleWriteSettingsLocked(); + } + } + } + + /** + * Remove UserPackage from the denied for handling a device + * + * @param device the device to remove denied packages from + * @param packageName the packages to remove + * @param user the user + */ + void removeDevicePackagesFromDenied(@NonNull UsbDevice device, @NonNull String[] packageNames, + @NonNull UserHandle user) { + DeviceFilter filter = new DeviceFilter(device); + + synchronized (mLock) { + ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.get(filter); + + if (userPackages != null) { + boolean shouldWrite = false; + for (String packageName : packageNames) { + UserPackage userPackage = new UserPackage(packageName, user); + + if (userPackages.contains(userPackage)) { + userPackages.remove(userPackage); + shouldWrite = true; + + if (userPackages.size() == 0) { + mDevicePreferenceDeniedMap.remove(filter); + break; + } + } + } + + if (shouldWrite) { + scheduleWriteSettingsLocked(); + } + } + } + } + + /** + * Remove UserPackage from the denied for handling a accessory + * + * @param accessory the accessory to remove denied packages from + * @param packageName the packages to remove + * @param user the user + */ + void removeAccessoryPackagesFromDenied(@NonNull UsbAccessory accessory, + @NonNull String[] packageNames, @NonNull UserHandle user) { + AccessoryFilter filter = new AccessoryFilter(accessory); + + synchronized (mLock) { + ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.get(filter); + + if (userPackages != null) { + boolean shouldWrite = false; + for (String packageName : packageNames) { + UserPackage userPackage = new UserPackage(packageName, user); + + if (userPackages.contains(userPackage)) { + userPackages.remove(userPackage); + shouldWrite = true; + + if (userPackages.size() == 0) { + mAccessoryPreferenceDeniedMap.remove(filter); + break; + } + } + } + + if (shouldWrite) { + scheduleWriteSettingsLocked(); + } + } + } + } + + /** * Set a package as default handler for a accessory. * * @param accessory The accessory that should be handled by default diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java index 077d6b9bd62d..095e8e9b7b5b 100644 --- a/services/usb/java/com/android/server/usb/UsbSerialReader.java +++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java @@ -39,7 +39,7 @@ import com.android.internal.util.ArrayUtils; class UsbSerialReader extends IUsbSerialReader.Stub { private final @Nullable String mSerialNumber; private final @NonNull Context mContext; - private final @NonNull UsbSettingsManager mSettingsManager; + private final @NonNull UsbPermissionManager mPermissionManager; private Object mDevice; @@ -51,10 +51,10 @@ class UsbSerialReader extends IUsbSerialReader.Stub { * @param settingsManager The USB settings manager * @param serialNumber The serial number that might be read */ - UsbSerialReader(@NonNull Context context, @NonNull UsbSettingsManager settingsManager, + UsbSerialReader(@NonNull Context context, @NonNull UsbPermissionManager permissionManager, @Nullable String serialNumber) { mContext = context; - mSettingsManager = settingsManager; + mPermissionManager = permissionManager; mSerialNumber = serialNumber; } @@ -75,12 +75,14 @@ class UsbSerialReader extends IUsbSerialReader.Stub { if (uid != Process.SYSTEM_UID) { enforcePackageBelongsToUid(uid, packageName); + UserHandle user = Binder.getCallingUserHandle(); int packageTargetSdkVersion; long token = Binder.clearCallingIdentity(); try { PackageInfo pkg; try { - pkg = mContext.getPackageManager().getPackageInfo(packageName, 0); + pkg = mContext.getPackageManager() + .getPackageInfoAsUser(packageName, 0, user.getIdentifier()); } catch (PackageManager.NameNotFoundException e) { throw new RemoteException("package " + packageName + " cannot be found"); } @@ -89,13 +91,14 @@ class UsbSerialReader extends IUsbSerialReader.Stub { if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) { if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid) == PackageManager.PERMISSION_DENIED) { - UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser( - UserHandle.getUserId(uid)); + int userId = UserHandle.getUserId(uid); if (mDevice instanceof UsbDevice) { - settings.checkPermission((UsbDevice) mDevice, packageName, pid, uid); + mPermissionManager.getPermissionsForUser(userId) + .checkPermission((UsbDevice) mDevice, packageName, pid, uid); } else { - settings.checkPermission((UsbAccessory) mDevice, uid); + mPermissionManager.getPermissionsForUser(userId) + .checkPermission((UsbAccessory) mDevice, uid); } } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 13275f34ee1a..14c7f04b9e82 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -56,6 +56,8 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.server.FgThread; +import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import java.io.File; @@ -64,6 +66,8 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; /** * UsbService manages all USB related state, including both host and device support. @@ -74,6 +78,9 @@ public class UsbService extends IUsbManager.Stub { public static class Lifecycle extends SystemService { private UsbService mUsbService; + private final CompletableFuture<Void> mOnStartFinished = new CompletableFuture<>(); + private final CompletableFuture<Void> mOnActivityManagerPhaseFinished = + new CompletableFuture<>(); public Lifecycle(Context context) { super(context); @@ -81,32 +88,41 @@ public class UsbService extends IUsbManager.Stub { @Override public void onStart() { - mUsbService = new UsbService(getContext()); - publishBinderService(Context.USB_SERVICE, mUsbService); + SystemServerInitThreadPool.submit(() -> { + mUsbService = new UsbService(getContext()); + publishBinderService(Context.USB_SERVICE, mUsbService); + mOnStartFinished.complete(null); + }, "UsbService$Lifecycle#onStart"); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { - mUsbService.systemReady(); + SystemServerInitThreadPool.submit(() -> { + mOnStartFinished.join(); + mUsbService.systemReady(); + mOnActivityManagerPhaseFinished.complete(null); + }, "UsbService$Lifecycle#onBootPhase"); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + mOnActivityManagerPhaseFinished.join(); mUsbService.bootCompleted(); } } @Override - public void onSwitchUser(int newUserId) { - mUsbService.onSwitchUser(newUserId); + public void onUserSwitching(TargetUser from, TargetUser to) { + FgThread.getHandler() + .postAtFrontOfQueue(() -> mUsbService.onSwitchUser(to.getUserIdentifier())); } @Override - public void onStopUser(int userHandle) { - mUsbService.onStopUser(UserHandle.of(userHandle)); + public void onUserStopping(TargetUser userInfo) { + mUsbService.onStopUser(userInfo.getUserHandle()); } @Override - public void onUnlockUser(int userHandle) { - mUsbService.onUnlockUser(userHandle); + public void onUserUnlocking(TargetUser userInfo) { + mUsbService.onUnlockUser(userInfo.getUserIdentifier()); } } @@ -121,6 +137,7 @@ public class UsbService extends IUsbManager.Stub { private final UsbAlsaManager mAlsaManager; private final UsbSettingsManager mSettingsManager; + private final UsbPermissionManager mPermissionManager; /** * The user id of the current user. There might be several profiles (with separate user ids) @@ -131,23 +148,35 @@ public class UsbService extends IUsbManager.Stub { private final Object mLock = new Object(); - private UsbUserSettingsManager getSettingsForUser(@UserIdInt int userIdInt) { - return mSettingsManager.getSettingsForUser(userIdInt); + /** + * @return the {@link UsbUserSettingsManager} for the given userId + */ + UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) { + return mSettingsManager.getSettingsForUser(userId); + } + + /** + * @return the {@link UsbUserPermissionManager} for the given userId + */ + UsbUserPermissionManager getPermissionsForUser(@UserIdInt int userId) { + return mPermissionManager.getPermissionsForUser(userId); } public UsbService(Context context) { mContext = context; mUserManager = context.getSystemService(UserManager.class); - mSettingsManager = new UsbSettingsManager(context); + mSettingsManager = new UsbSettingsManager(context, this); + mPermissionManager = new UsbPermissionManager(context, this); mAlsaManager = new UsbAlsaManager(context); final PackageManager pm = mContext.getPackageManager(); if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) { - mHostManager = new UsbHostManager(context, mAlsaManager, mSettingsManager); + mHostManager = new UsbHostManager(context, mAlsaManager, mPermissionManager); } if (new File("/sys/class/android_usb").exists()) { - mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager); + mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager, + mPermissionManager); } if (mHostManager != null || mDeviceManager != null) { mPortManager = new UsbPortManager(context); @@ -256,7 +285,7 @@ public class UsbService extends IUsbManager.Stub { try { synchronized (mLock) { if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) { - fd = mHostManager.openDevice(deviceName, getSettingsForUser(user), + fd = mHostManager.openDevice(deviceName, getPermissionsForUser(user), packageName, pid, uid); } else { Slog.w(TAG, "Cannot open " + deviceName + " for user " + user @@ -293,7 +322,7 @@ public class UsbService extends IUsbManager.Stub { try { synchronized (mLock) { if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) { - return mDeviceManager.openAccessory(accessory, getSettingsForUser(user), + return mDeviceManager.openAccessory(accessory, getPermissionsForUser(user), uid); } else { Slog.w(TAG, "Cannot open " + accessory + " for user " + user @@ -317,7 +346,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setDevicePackage(UsbDevice device, String packageName, int userId) { - device = Preconditions.checkNotNull(device); + Objects.requireNonNull(device); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -333,7 +362,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) { - accessory = Preconditions.checkNotNull(accessory); + Objects.requireNonNull(accessory); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -349,6 +378,112 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void addDevicePackagesToPreferenceDenied(UsbDevice device, String[] packageNames, + UserHandle user) { + Objects.requireNonNull(device); + packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mSettingsManager.getSettingsForProfileGroup(user) + .addDevicePackagesToDenied(device, packageNames, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void addAccessoryPackagesToPreferenceDenied(UsbAccessory accessory, + String[] packageNames, UserHandle user) { + Objects.requireNonNull(accessory); + packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mSettingsManager.getSettingsForProfileGroup(user) + .addAccessoryPackagesToDenied(accessory, packageNames, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void removeDevicePackagesFromPreferenceDenied(UsbDevice device, String[] packageNames, + UserHandle user) { + Objects.requireNonNull(device); + packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mSettingsManager.getSettingsForProfileGroup(user) + .removeDevicePackagesFromDenied(device, packageNames, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void removeAccessoryPackagesFromPreferenceDenied(UsbAccessory accessory, + String[] packageNames, UserHandle user) { + Objects.requireNonNull(accessory); + packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames"); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mSettingsManager.getSettingsForProfileGroup(user) + .removeAccessoryPackagesFromDenied(accessory, packageNames, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setDevicePersistentPermission(UsbDevice device, int uid, UserHandle user, + boolean shouldBeGranted) { + Objects.requireNonNull(device); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mPermissionManager.getPermissionsForUser(user).setDevicePersistentPermission(device, + uid, shouldBeGranted); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setAccessoryPersistentPermission(UsbAccessory accessory, int uid, + UserHandle user, boolean shouldBeGranted) { + Objects.requireNonNull(accessory); + Objects.requireNonNull(user); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long token = Binder.clearCallingIdentity(); + try { + mPermissionManager.getPermissionsForUser(user).setAccessoryPersistentPermission( + accessory, uid, shouldBeGranted); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public boolean hasDevicePermission(UsbDevice device, String packageName) { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -356,7 +491,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - return getSettingsForUser(userId).hasPermission(device, packageName, pid, uid); + return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -369,7 +504,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - return getSettingsForUser(userId).hasPermission(accessory, uid); + return getPermissionsForUser(userId).hasPermission(accessory, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -383,7 +518,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - getSettingsForUser(userId).requestPermission(device, packageName, pi, pid, uid); + getPermissionsForUser(userId).requestPermission(device, packageName, pi, pid, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -397,7 +532,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - getSettingsForUser(userId).requestPermission(accessory, packageName, pi, uid); + getPermissionsForUser(userId).requestPermission(accessory, packageName, pi, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -410,7 +545,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - getSettingsForUser(userId).grantDevicePermission(device, uid); + getPermissionsForUser(userId).grantDevicePermission(device, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -423,7 +558,7 @@ public class UsbService extends IUsbManager.Stub { final long token = Binder.clearCallingIdentity(); try { - getSettingsForUser(userId).grantAccessoryPermission(accessory, uid); + getPermissionsForUser(userId).grantAccessoryPermission(accessory, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -503,6 +638,19 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void resetUsbGadget() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null"); + + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceManager.resetUsbGadget(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public List<ParcelableUsbPort> getPorts() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -529,7 +677,7 @@ public class UsbService extends IUsbManager.Stub { @Override public UsbPortStatus getPortStatus(String portId) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); @@ -542,7 +690,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void setPortRoles(String portId, int powerRole, int dataRole) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); UsbPort.checkRoles(powerRole, dataRole); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -558,7 +706,7 @@ public class UsbService extends IUsbManager.Stub { @Override public void enableContaminantDetection(String portId, boolean enable) { - Preconditions.checkNotNull(portId, "portId must not be null"); + Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); @@ -624,6 +772,8 @@ public class UsbService extends IUsbManager.Stub { mSettingsManager.dump(dump, "settings_manager", UsbServiceDumpProto.SETTINGS_MANAGER); + mPermissionManager.dump(dump, "permissions_manager", + UsbServiceDumpProto.PERMISSIONS_MANAGER); dump.flush(); } else if ("set-port-roles".equals(args[0]) && args.length == 4) { final String portId = args[1]; diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java index 27566f04c280..7b677eea6b8f 100644 --- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java @@ -19,15 +19,10 @@ package com.android.server.usb; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; -import android.content.Intent; import android.content.pm.UserInfo; -import android.hardware.usb.UsbAccessory; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; import android.os.UserHandle; import android.os.UserManager; import android.service.usb.UsbSettingsManagerProto; -import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -59,8 +54,11 @@ class UsbSettingsManager { private UserManager mUserManager; private UsbHandlerManager mUsbHandlerManager; - public UsbSettingsManager(@NonNull Context context) { + final UsbService mUsbService; + + UsbSettingsManager(@NonNull Context context, UsbService usbService) { mContext = context; + mUsbService = usbService; mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mUsbHandlerManager = new UsbHandlerManager(context); } @@ -76,8 +74,7 @@ class UsbSettingsManager { synchronized (mSettingsByUser) { UsbUserSettingsManager settings = mSettingsByUser.get(userId); if (settings == null) { - settings = new UsbUserSettingsManager(mContext, UserHandle.of(userId), - new UsbPermissionManager(mContext, UserHandle.of(userId))); + settings = new UsbUserSettingsManager(mContext, UserHandle.of(userId)); mSettingsByUser.put(userId, settings); } return settings; @@ -133,7 +130,7 @@ class UsbSettingsManager { // it from all profile groups. int numProfileGroups = mSettingsByProfileGroup.size(); for (int i = 0; i < numProfileGroups; i++) { - mSettingsByProfileGroup.valueAt(i).removeAllDefaultsForUser(userToRemove); + mSettingsByProfileGroup.valueAt(i).removeUser(userToRemove); } } } @@ -164,46 +161,4 @@ class UsbSettingsManager { dump.end(token); } - - /** - * Remove temporary access permission and broadcast that a device was removed. - * - * @param device The device that is removed - */ - void usbDeviceRemoved(@NonNull UsbDevice device) { - synchronized (mSettingsByUser) { - for (int i = 0; i < mSettingsByUser.size(); i++) { - // clear temporary permissions for the device - mSettingsByUser.valueAt(i).removeDevicePermissions(device); - } - } - - Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); - intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(UsbManager.EXTRA_DEVICE, device); - - if (DEBUG) { - Slog.d(LOG_TAG, "usbDeviceRemoved, sending " + intent); - } - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - } - - /** - * Remove temporary access permission and broadcast that a accessory was removed. - * - * @param accessory The accessory that is removed - */ - void usbAccessoryRemoved(@NonNull UsbAccessory accessory) { - synchronized (mSettingsByUser) { - for (int i = 0; i < mSettingsByUser.size(); i++) { - // clear temporary permissions for the accessory - mSettingsByUser.valueAt(i).removeAccessoryPermissions(accessory); - } - } - - Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_DETACHED); - intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - } } diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java new file mode 100644 index 000000000000..333edfd91b16 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2018 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.annotation.NonNull; +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.hardware.usb.AccessoryFilter; +import android.hardware.usb.DeviceFilter; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Environment; +import android.os.Process; +import android.os.UserHandle; +import android.service.usb.UsbAccessoryPermissionProto; +import android.service.usb.UsbAccessoryPersistentPermissionProto; +import android.service.usb.UsbDevicePermissionProto; +import android.service.usb.UsbDevicePersistentPermissionProto; +import android.service.usb.UsbUidPermissionProto; +import android.service.usb.UsbUserPermissionsManagerProto; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import com.android.internal.util.dump.DualDumpOutputStream; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * UsbUserPermissionManager manages usb device or accessory access permissions. + * + * @hide + */ +class UsbUserPermissionManager { + private static final String TAG = UsbUserPermissionManager.class.getSimpleName(); + private static final boolean DEBUG = false; + + @GuardedBy("mLock") + /** Mapping of USB device name to list of UIDs with permissions for the device + * Each entry lasts until device is disconnected*/ + private final ArrayMap<String, SparseBooleanArray> mDevicePermissionMap = + new ArrayMap<>(); + @GuardedBy("mLock") + /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory + * Each entry lasts until accessory is disconnected*/ + private final ArrayMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap = + new ArrayMap<>(); + + @GuardedBy("mLock") + /** Maps USB device to list of UIDs with persistent permissions for the device*/ + private final ArrayMap<DeviceFilter, SparseBooleanArray> + mDevicePersistentPermissionMap = new ArrayMap<>(); + @GuardedBy("mLock") + /** Maps Usb Accessory to list of UIDs with persistent permissions for the accessory*/ + private final ArrayMap<AccessoryFilter, SparseBooleanArray> + mAccessoryPersistentPermissionMap = new ArrayMap<>(); + + private final Context mContext; + private final UserHandle mUser; + private final UsbUserSettingsManager mUsbUserSettingsManager; + private final boolean mDisablePermissionDialogs; + + private final @NonNull AtomicFile mPermissionsFile; + + private final Object mLock = new Object(); + + /** + * If a async task to persist the mDevicePersistentPreferenceMap and + * mAccessoryPersistentPreferenceMap is currently scheduled. + */ + @GuardedBy("mLock") + private boolean mIsCopyPermissionsScheduled; + + UsbUserPermissionManager(@NonNull Context context, + @NonNull UsbUserSettingsManager usbUserSettingsManager) { + mContext = context; + mUser = context.getUser(); + mUsbUserSettingsManager = usbUserSettingsManager; + mDisablePermissionDialogs = context.getResources().getBoolean( + com.android.internal.R.bool.config_disableUsbPermissionDialogs); + + mPermissionsFile = new AtomicFile(new File( + Environment.getUserSystemDirectory(mUser.getIdentifier()), + "usb_permissions.xml"), "usb-permissions"); + + synchronized (mLock) { + readPermissionsLocked(); + } + } + + /** + * Removes access permissions of all packages for the USB accessory. + * + * @param accessory to remove permissions for + */ + void removeAccessoryPermissions(@NonNull UsbAccessory accessory) { + synchronized (mLock) { + mAccessoryPermissionMap.remove(accessory); + } + } + + /** + * Removes access permissions of all packages for the USB device. + * + * @param device to remove permissions for + */ + void removeDevicePermissions(@NonNull UsbDevice device) { + synchronized (mLock) { + mDevicePermissionMap.remove(device.getDeviceName()); + } + } + + /** + * Grants permission for USB device without showing system dialog for package with uid. + * + * @param device to grant permission for + * @param uid to grant permission for + */ + void grantDevicePermission(@NonNull 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); + } + } + + /** + * Grants permission for USB accessory without showing system dialog for package with uid. + * + * @param accessory to grant permission for + * @param uid to grant permission for + */ + void grantAccessoryPermission(@NonNull 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); + } + } + + /** + * Returns true if package with uid has permission to access the device. + * + * @param device to check permission for + * @param pid to check permission for + * @param uid to check permission for + * @return {@code true} if package with uid has permission + */ + boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int pid, + int uid) { + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, pid, uid)) { + return false; + } + } + synchronized (mLock) { + if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { + return true; + } + DeviceFilter filter = new DeviceFilter(device); + SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter); + if (permissionsForDevice != null) { + int idx = permissionsForDevice.indexOfKey(uid); + if (idx >= 0) { + return permissionsForDevice.valueAt(idx); + } + } + SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName()); + if (uidList == null) { + return false; + } + return uidList.get(uid); + } + } + + /** + * Returns true if caller has permission to access the accessory. + * + * @param accessory to check permission for + * @param uid to check permission for + * @return {@code true} if caller has permssion + */ + boolean hasPermission(@NonNull UsbAccessory accessory, int uid) { + synchronized (mLock) { + if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) { + return true; + } + AccessoryFilter filter = new AccessoryFilter(accessory); + SparseBooleanArray permissionsForAccessory = + mAccessoryPersistentPermissionMap.get(filter); + if (permissionsForAccessory != null) { + int idx = permissionsForAccessory.indexOfKey(uid); + if (idx >= 0) { + return permissionsForAccessory.valueAt(idx); + } + } + SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory); + if (uidList == null) { + return false; + } + return uidList.get(uid); + } + } + + void setDevicePersistentPermission(@NonNull UsbDevice device, int uid, boolean isGranted) { + + boolean isChanged; + DeviceFilter filter = new DeviceFilter(device); + synchronized (mLock) { + SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter); + if (permissionsForDevice == null) { + permissionsForDevice = new SparseBooleanArray(); + mDevicePersistentPermissionMap.put(filter, permissionsForDevice); + } + int idx = permissionsForDevice.indexOfKey(uid); + if (idx >= 0) { + isChanged = permissionsForDevice.valueAt(idx) != isGranted; + permissionsForDevice.setValueAt(idx, isGranted); + } else { + isChanged = true; + permissionsForDevice.put(uid, isGranted); + } + + if (isChanged) { + scheduleWritePermissionsLocked(); + } + } + } + + void setAccessoryPersistentPermission(@NonNull UsbAccessory accessory, int uid, + boolean isGranted) { + + boolean isChanged; + AccessoryFilter filter = new AccessoryFilter(accessory); + synchronized (mLock) { + SparseBooleanArray permissionsForAccessory = + mAccessoryPersistentPermissionMap.get(filter); + if (permissionsForAccessory == null) { + permissionsForAccessory = new SparseBooleanArray(); + mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory); + } + int idx = permissionsForAccessory.indexOfKey(uid); + if (idx >= 0) { + isChanged = permissionsForAccessory.valueAt(idx) != isGranted; + permissionsForAccessory.setValueAt(idx, isGranted); + } else { + isChanged = true; + permissionsForAccessory.put(uid, isGranted); + } + + if (isChanged) { + scheduleWritePermissionsLocked(); + } + } + } + + private void readPermission(@NonNull XmlPullParser parser) throws XmlPullParserException, + IOException { + int uid; + boolean isGranted; + + try { + uid = XmlUtils.readIntAttribute(parser, "uid"); + } catch (NumberFormatException e) { + Slog.e(TAG, "error reading usb permission uid", e); + XmlUtils.skipCurrentTag(parser); + return; + } + + // only use "true"/"false" as valid values + String isGrantedString = parser.getAttributeValue(null, "granted"); + if (isGrantedString == null || !(isGrantedString.equals(Boolean.TRUE.toString()) + || isGrantedString.equals(Boolean.FALSE.toString()))) { + Slog.e(TAG, "error reading usb permission granted state"); + XmlUtils.skipCurrentTag(parser); + return; + } + isGranted = isGrantedString.equals(Boolean.TRUE.toString()); + XmlUtils.nextElement(parser); + if ("usb-device".equals(parser.getName())) { + DeviceFilter filter = DeviceFilter.read(parser); + int idx = mDevicePersistentPermissionMap.indexOfKey(filter); + if (idx >= 0) { + SparseBooleanArray permissionsForDevice = + mDevicePersistentPermissionMap.valueAt(idx); + permissionsForDevice.put(uid, isGranted); + } else { + SparseBooleanArray permissionsForDevice = new SparseBooleanArray(); + mDevicePersistentPermissionMap.put(filter, permissionsForDevice); + permissionsForDevice.put(uid, isGranted); + } + } else if ("usb-accessory".equals(parser.getName())) { + AccessoryFilter filter = AccessoryFilter.read(parser); + int idx = mAccessoryPersistentPermissionMap.indexOfKey(filter); + if (idx >= 0) { + SparseBooleanArray permissionsForAccessory = + mAccessoryPersistentPermissionMap.valueAt(idx); + permissionsForAccessory.put(uid, isGranted); + } else { + SparseBooleanArray permissionsForAccessory = new SparseBooleanArray(); + mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory); + permissionsForAccessory.put(uid, isGranted); + } + } + } + + @GuardedBy("mLock") + private void readPermissionsLocked() { + mDevicePersistentPermissionMap.clear(); + mAccessoryPersistentPermissionMap.clear(); + + try (FileInputStream in = mPermissionsFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("permission".equals(tagName)) { + readPermission(parser); + } else { + XmlUtils.nextElement(parser); + } + } + } catch (FileNotFoundException e) { + if (DEBUG) Slog.d(TAG, "usb permissions file not found"); + } catch (Exception e) { + Slog.e(TAG, "error reading usb permissions file, deleting to start fresh", e); + mPermissionsFile.delete(); + } + } + + @GuardedBy("mLock") + private void scheduleWritePermissionsLocked() { + if (mIsCopyPermissionsScheduled) { + return; + } + mIsCopyPermissionsScheduled = true; + + AsyncTask.execute(() -> { + int numDevices; + DeviceFilter[] devices; + int[][] uidsForDevices; + boolean[][] grantedValuesForDevices; + + int numAccessories; + AccessoryFilter[] accessories; + int[][] uidsForAccessories; + boolean[][] grantedValuesForAccessories; + + synchronized (mLock) { + // Copy the permission state so we can write outside of lock + numDevices = mDevicePersistentPermissionMap.size(); + devices = new DeviceFilter[numDevices]; + uidsForDevices = new int[numDevices][]; + grantedValuesForDevices = new boolean[numDevices][]; + for (int deviceIdx = 0; deviceIdx < numDevices; deviceIdx++) { + devices[deviceIdx] = + new DeviceFilter(mDevicePersistentPermissionMap.keyAt(deviceIdx)); + SparseBooleanArray permissions = + mDevicePersistentPermissionMap.valueAt(deviceIdx); + int numPermissions = permissions.size(); + uidsForDevices[deviceIdx] = new int[numPermissions]; + grantedValuesForDevices[deviceIdx] = new boolean[numPermissions]; + for (int permissionIdx = 0; permissionIdx < numPermissions; permissionIdx++) { + uidsForDevices[deviceIdx][permissionIdx] = permissions.keyAt(permissionIdx); + grantedValuesForDevices[deviceIdx][permissionIdx] = + permissions.valueAt(permissionIdx); + } + } + + numAccessories = mAccessoryPersistentPermissionMap.size(); + accessories = new AccessoryFilter[numAccessories]; + uidsForAccessories = new int[numAccessories][]; + grantedValuesForAccessories = new boolean[numAccessories][]; + for (int accessoryIdx = 0; accessoryIdx < numAccessories; accessoryIdx++) { + accessories[accessoryIdx] = new AccessoryFilter( + mAccessoryPersistentPermissionMap.keyAt(accessoryIdx)); + SparseBooleanArray permissions = + mAccessoryPersistentPermissionMap.valueAt(accessoryIdx); + int numPermissions = permissions.size(); + uidsForAccessories[accessoryIdx] = new int[numPermissions]; + grantedValuesForAccessories[accessoryIdx] = new boolean[numPermissions]; + for (int permissionIdx = 0; permissionIdx < numPermissions; permissionIdx++) { + uidsForAccessories[accessoryIdx][permissionIdx] = + permissions.keyAt(permissionIdx); + grantedValuesForAccessories[accessoryIdx][permissionIdx] = + permissions.valueAt(permissionIdx); + } + } + mIsCopyPermissionsScheduled = false; + } + + synchronized (mPermissionsFile) { + FileOutputStream out = null; + try { + out = mPermissionsFile.startWrite(); + FastXmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.startTag(null, "permissions"); + + for (int i = 0; i < numDevices; i++) { + int numPermissions = uidsForDevices[i].length; + for (int j = 0; j < numPermissions; j++) { + serializer.startTag(null, "permission"); + serializer.attribute(null, "uid", + Integer.toString(uidsForDevices[i][j])); + serializer.attribute(null, "granted", + Boolean.toString(grantedValuesForDevices[i][j])); + devices[i].write(serializer); + serializer.endTag(null, "permission"); + } + } + + for (int i = 0; i < numAccessories; i++) { + int numPermissions = uidsForAccessories[i].length; + for (int j = 0; j < numPermissions; j++) { + serializer.startTag(null, "permission"); + serializer.attribute(null, "uid", + Integer.toString(uidsForAccessories[i][j])); + serializer.attribute(null, "granted", + Boolean.toString(grantedValuesForDevices[i][j])); + accessories[i].write(serializer); + serializer.endTag(null, "permission"); + } + } + + serializer.endTag(null, "permissions"); + serializer.endDocument(); + + mPermissionsFile.finishWrite(out); + } catch (IOException e) { + Slog.e(TAG, "Failed to write permissions", e); + if (out != null) { + mPermissionsFile.failWrite(out); + } + } + } + }); + } + + /** + * Creates UI dialog to request permission for the given package to access the device + * or accessory. + * + * @param device The USB device attached + * @param accessory The USB accessory attached + * @param canBeDefault Whether the calling pacakge can set as default handler + * of the USB device or accessory + * @param packageName The package name of the calling package + * @param uid The uid of the calling package + * @param userContext The context to start the UI dialog + * @param pi PendingIntent for returning result + */ + void requestPermissionDialog(@Nullable UsbDevice device, + @Nullable UsbAccessory accessory, + boolean canBeDefault, + @NonNull String packageName, + int uid, + @NonNull Context userContext, + @NonNull PendingIntent pi) { + long identity = Binder.clearCallingIdentity(); + Intent intent = new Intent(); + if (device != null) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + } else { + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + } + intent.putExtra(Intent.EXTRA_INTENT, pi); + intent.putExtra(Intent.EXTRA_UID, uid); + intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault); + intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName); + intent.setComponent(ComponentName.unflattenFromString(userContext.getResources().getString( + com.android.internal.R.string.config_usbPermissionActivity))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + try { + userContext.startActivityAsUser(intent, mUser); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "unable to start UsbPermissionActivity"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { + long token = dump.start(idName, id); + synchronized (mLock) { + dump.write("user_id", UsbUserPermissionsManagerProto.USER_ID, mUser.getIdentifier()); + int numMappings = mDevicePermissionMap.size(); + for (int mappingsIdx = 0; mappingsIdx < numMappings; mappingsIdx++) { + String deviceName = mDevicePermissionMap.keyAt(mappingsIdx); + long devicePermissionToken = dump.start("device_permissions", + UsbUserPermissionsManagerProto.DEVICE_PERMISSIONS); + + dump.write("device_name", UsbDevicePermissionProto.DEVICE_NAME, deviceName); + + SparseBooleanArray uidList = mDevicePermissionMap.valueAt(mappingsIdx); + int numUids = uidList.size(); + for (int uidsIdx = 0; uidsIdx < numUids; uidsIdx++) { + dump.write("uids", UsbDevicePermissionProto.UIDS, uidList.keyAt(uidsIdx)); + } + + dump.end(devicePermissionToken); + } + + numMappings = mAccessoryPermissionMap.size(); + for (int mappingsIdx = 0; mappingsIdx < numMappings; ++mappingsIdx) { + UsbAccessory accessory = mAccessoryPermissionMap.keyAt(mappingsIdx); + long accessoryPermissionToken = dump.start("accessory_permissions", + UsbUserPermissionsManagerProto.ACCESSORY_PERMISSIONS); + + dump.write("accessory_description", + UsbAccessoryPermissionProto.ACCESSORY_DESCRIPTION, + accessory.getDescription()); + + SparseBooleanArray uidList = mAccessoryPermissionMap.valueAt(mappingsIdx); + int numUids = uidList.size(); + for (int uidsIdx = 0; uidsIdx < numUids; uidsIdx++) { + dump.write("uids", UsbAccessoryPermissionProto.UIDS, uidList.keyAt(uidsIdx)); + } + + dump.end(accessoryPermissionToken); + } + + numMappings = mDevicePersistentPermissionMap.size(); + for (int mappingsIdx = 0; mappingsIdx < numMappings; mappingsIdx++) { + DeviceFilter filter = mDevicePersistentPermissionMap.keyAt(mappingsIdx); + long devicePermissionToken = dump.start("device_persistent_permissions", + UsbUserPermissionsManagerProto.DEVICE_PERSISTENT_PERMISSIONS); + filter.dump(dump, "device", + UsbDevicePersistentPermissionProto.DEVICE_FILTER); + SparseBooleanArray permissions = + mDevicePersistentPermissionMap.valueAt(mappingsIdx); + int numPermissions = permissions.size(); + for (int permissionsIdx = 0; permissionsIdx < numPermissions; permissionsIdx++) { + long uidPermissionToken = dump.start("uid_permission", + UsbDevicePersistentPermissionProto.PERMISSION_VALUES); + dump.write("uid", UsbUidPermissionProto.UID, permissions.keyAt(permissionsIdx)); + dump.write("is_granted", + UsbUidPermissionProto.IS_GRANTED, permissions.valueAt(permissionsIdx)); + dump.end(uidPermissionToken); + } + dump.end(devicePermissionToken); + } + + numMappings = mAccessoryPersistentPermissionMap.size(); + for (int mappingsIdx = 0; mappingsIdx < numMappings; mappingsIdx++) { + AccessoryFilter filter = mAccessoryPersistentPermissionMap.keyAt(mappingsIdx); + long accessoryPermissionToken = dump.start("accessory_persistent_permissions", + UsbUserPermissionsManagerProto.ACCESSORY_PERSISTENT_PERMISSIONS); + filter.dump(dump, "accessory", + UsbAccessoryPersistentPermissionProto.ACCESSORY_FILTER); + SparseBooleanArray permissions = + mAccessoryPersistentPermissionMap.valueAt(mappingsIdx); + int numPermissions = permissions.size(); + for (int permissionsIdx = 0; permissionsIdx < numPermissions; permissionsIdx++) { + long uidPermissionToken = dump.start("uid_permission", + UsbAccessoryPersistentPermissionProto.PERMISSION_VALUES); + dump.write("uid", UsbUidPermissionProto.UID, permissions.keyAt(permissionsIdx)); + dump.write("is_granted", + UsbUidPermissionProto.IS_GRANTED, permissions.valueAt(permissionsIdx)); + dump.end(uidPermissionToken); + } + dump.end(accessoryPermissionToken); + } + } + dump.end(token); + } + + /** + * Check for camera permission of the calling process. + * + * @param packageName Package name of the caller. + * @param pid Linux pid of the calling process. + * @param uid Linux uid of the calling process. + * @return True in case camera permission is available, False otherwise. + */ + private boolean isCameraPermissionGranted(String packageName, int pid, int uid) { + int targetSdkVersion = android.os.Build.VERSION_CODES.P; + try { + ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0); + // compare uid with packageName to foil apps pretending to be someone else + if (aInfo.uid != uid) { + Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); + return false; + } + targetSdkVersion = aInfo.targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + Slog.i(TAG, "Package not found, likely due to invalid package name!"); + return false; + } + + if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { + int allowed = mContext.checkPermission(android.Manifest.permission.CAMERA, pid, uid); + if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { + Slog.i(TAG, "Camera permission required for USB video class devices"); + return false; + } + } + + return true; + } + + public void checkPermission(UsbDevice device, String packageName, int pid, int uid) { + if (!hasPermission(device, packageName, pid, uid)) { + throw new SecurityException("User has not given " + uid + "/" + packageName + + " permission to access device " + device.getDeviceName()); + } + } + + public void checkPermission(UsbAccessory accessory, int uid) { + if (!hasPermission(accessory, uid)) { + throw new SecurityException("User has not given " + uid + " permission to accessory " + + accessory); + } + } + + private void requestPermissionDialog(@Nullable UsbDevice device, + @Nullable UsbAccessory accessory, + boolean canBeDefault, + String packageName, + PendingIntent pi, + int uid) { + // compare uid with packageName to foil apps pretending to be someone else + try { + ApplicationInfo aInfo = mContext.getPackageManager().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"); + } + + requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi); + } + + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int pid, + int uid) { + Intent intent = new Intent(); + + // respond immediately if permission has already been granted + if (hasPermission(device, packageName, pid, uid)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + if (isCameraDevicePresent(device)) { + if (!isCameraPermissionGranted(packageName, pid, uid)) { + intent.putExtra(UsbManager.EXTRA_DEVICE, device); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + } + + requestPermissionDialog(device, null, + mUsbUserSettingsManager.canBeDefault(device, packageName), packageName, pi, uid); + } + + public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi, + int uid) { + // respond immediately if permission has already been granted + if (hasPermission(accessory, uid)) { + Intent intent = new Intent(); + intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); + intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); + try { + pi.send(mContext, 0, intent); + } catch (PendingIntent.CanceledException e) { + if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); + } + return; + } + + requestPermissionDialog(null, accessory, + mUsbUserSettingsManager.canBeDefault(accessory, packageName), packageName, pi, uid); + } + + /** + * Check whether a particular device or any of its interfaces + * is of class VIDEO. + * + * @param device The device that needs to get scanned + * @return True in case a VIDEO device or interface is present, + * False otherwise. + */ + private boolean isCameraDevicePresent(UsbDevice device) { + if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface iface = device.getInterface(i); + if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { + return true; + } + } + + return false; + } +} diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java index e1bfb8a7c6d0..c2b8d0109e68 100644 --- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java @@ -21,13 +21,10 @@ import static com.android.server.usb.UsbProfileGroupSettingsManager.getAccessory import static com.android.server.usb.UsbProfileGroupSettingsManager.getDeviceFilters; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; 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; @@ -36,9 +33,7 @@ import android.content.res.XmlResourceParser; import android.hardware.usb.AccessoryFilter; import android.hardware.usb.DeviceFilter; import android.hardware.usb.UsbAccessory; -import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.UserHandle; import android.service.usb.UsbAccessoryAttachedActivities; @@ -62,12 +57,10 @@ class UsbUserSettingsManager { private final Context mUserContext; private final PackageManager mPackageManager; - private final UsbPermissionManager mUsbPermissionManager; private final Object mLock = new Object(); - UsbUserSettingsManager(Context context, UserHandle user, - @NonNull UsbPermissionManager usbPermissionManager) { + UsbUserSettingsManager(Context context, UserHandle user) { if (DEBUG) Slog.v(TAG, "Creating settings for " + user); try { @@ -79,192 +72,6 @@ class UsbUserSettingsManager { mPackageManager = mUserContext.getPackageManager(); mUser = user; - mUsbPermissionManager = usbPermissionManager; - } - - /** - * Remove all access permission for a device. - * - * @param device The device the permissions are for - */ - void removeDevicePermissions(@NonNull UsbDevice device) { - mUsbPermissionManager.removeDevicePermissions(device); - } - - /** - * Remove all access permission for a accessory. - * - * @param accessory The accessory the permissions are for - */ - void removeAccessoryPermissions(@NonNull UsbAccessory accessory) { - mUsbPermissionManager.removeAccessoryPermissions(accessory); - } - - /** - * Check whether a particular device or any of its interfaces - * is of class VIDEO. - * - * @param device The device that needs to get scanned - * @return True in case a VIDEO device or interface is present, - * False otherwise. - */ - private boolean isCameraDevicePresent(UsbDevice device) { - if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) { - return true; - } - - for (int i = 0; i < device.getInterfaceCount(); i++) { - UsbInterface iface = device.getInterface(i); - if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) { - return true; - } - } - - return false; - } - - /** - * Check for camera permission of the calling process. - * - * @param packageName Package name of the caller. - * @param pid Linux pid of the calling process. - * @param uid Linux uid of the calling process. - * - * @return True in case camera permission is available, False otherwise. - */ - private boolean isCameraPermissionGranted(String packageName, int pid, int uid) { - int targetSdkVersion = android.os.Build.VERSION_CODES.P; - try { - ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0); - // compare uid with packageName to foil apps pretending to be someone else - if (aInfo.uid != uid) { - Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid); - return false; - } - targetSdkVersion = aInfo.targetSdkVersion; - } catch (PackageManager.NameNotFoundException e) { - Slog.i(TAG, "Package not found, likely due to invalid package name!"); - return false; - } - - if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { - int allowed = mUserContext.checkPermission(android.Manifest.permission.CAMERA, pid, - uid); - if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { - Slog.i(TAG, "Camera permission required for USB video class devices"); - return false; - } - } - - return true; - } - - public boolean hasPermission(UsbDevice device, String packageName, int pid, int uid) { - if (isCameraDevicePresent(device)) { - if (!isCameraPermissionGranted(packageName, pid, uid)) { - return false; - } - } - - return mUsbPermissionManager.hasPermission(device, uid); - } - - public boolean hasPermission(UsbAccessory accessory, int uid) { - return mUsbPermissionManager.hasPermission(accessory, uid); - } - - public void checkPermission(UsbDevice device, String packageName, int pid, int uid) { - if (!hasPermission(device, packageName, pid, uid)) { - throw new SecurityException("User has not given " + uid + "/" + packageName - + " permission to access device " + device.getDeviceName()); - } - } - - public void checkPermission(UsbAccessory accessory, int uid) { - if (!hasPermission(accessory, uid)) { - throw new SecurityException("User has not given " + uid + " permission to accessory " - + accessory); - } - } - - private void requestPermissionDialog(@Nullable UsbDevice device, - @Nullable UsbAccessory accessory, - boolean canBeDefault, - String packageName, - PendingIntent pi, - int uid) { - // 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"); - } - - mUsbPermissionManager.requestPermissionDialog(device, - accessory, canBeDefault, packageName, uid, mUserContext, pi); - } - - public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int pid, - int uid) { - Intent intent = new Intent(); - - // respond immediately if permission has already been granted - if (hasPermission(device, packageName, pid, uid)) { - 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; - } - if (isCameraDevicePresent(device)) { - if (!isCameraPermissionGranted(packageName, pid, uid)) { - intent.putExtra(UsbManager.EXTRA_DEVICE, device); - intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); - try { - pi.send(mUserContext, 0, intent); - } catch (PendingIntent.CanceledException e) { - if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); - } - return; - } - } - - requestPermissionDialog(device, null, canBeDefault(device, packageName), packageName, pi, - uid); - } - - public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi, - int uid) { - // respond immediately if permission has already been granted - if (hasPermission(accessory, uid)) { - Intent intent = new Intent(); - 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; - } - - requestPermissionDialog(null, accessory, canBeDefault(accessory, packageName), packageName, - pi, uid); - } - - public void grantDevicePermission(UsbDevice device, int uid) { - mUsbPermissionManager.grantDevicePermission(device, uid); - } - - public void grantAccessoryPermission(UsbAccessory accessory, int uid) { - mUsbPermissionManager.grantAccessoryPermission(accessory, uid); } /** @@ -288,7 +95,7 @@ class UsbUserSettingsManager { * * @return {@code true} if the app can be default */ - private boolean canBeDefault(@NonNull UsbDevice device, String packageName) { + boolean canBeDefault(@NonNull UsbDevice device, String packageName) { ActivityInfo[] activities = getPackageActivities(packageName); if (activities != null) { int numActivities = activities.length; @@ -330,7 +137,7 @@ class UsbUserSettingsManager { * * @return {@code true} if the app can be default */ - private boolean canBeDefault(@NonNull UsbAccessory accessory, String packageName) { + boolean canBeDefault(@NonNull UsbAccessory accessory, String packageName) { ActivityInfo[] activities = getPackageActivities(packageName); if (activities != null) { int numActivities = activities.length; @@ -380,8 +187,6 @@ class UsbUserSettingsManager { synchronized (mLock) { dump.write("user_id", UsbUserSettingsManagerProto.USER_ID, mUser.getIdentifier()); - mUsbPermissionManager.dump(dump); - List<ResolveInfo> deviceAttachedActivities = queryIntentActivities( new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED)); int numDeviceAttachedActivities = deviceAttachedActivities.size(); diff --git a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java index 1e823b63d5b2..56dc3e05a240 100644 --- a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java +++ b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java @@ -185,12 +185,14 @@ public final class ByteStream { // Positive offsets only throw new IllegalArgumentException(); } - // do arithmetic and comparison in long to ovoid potention integer overflow + // do arithmetic and comparison in long to avoid potential integer overflow long longNewIndex = (long) mIndex + (long) numBytes; - if (longNewIndex < (long) mBytes.length) { + if (longNewIndex <= (long) mBytes.length) { mReadCount += numBytes; mIndex += numBytes; } else { + // Position the stream to the end so available() will return 0 + mIndex = mBytes.length; throw new IndexOutOfBoundsException(); } } @@ -210,6 +212,7 @@ public final class ByteStream { mReadCount -= numBytes; mIndex -= numBytes; } else { + mIndex = 0; throw new IndexOutOfBoundsException(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java index 7ebccf39868c..ff7f3934086c 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -45,7 +45,6 @@ abstract class UsbACEndpoint extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { mSubtype = stream.getByte(); - return mLength; } @@ -53,14 +52,24 @@ abstract class UsbACEndpoint extends UsbDescriptor { int length, byte type) { UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); int subClass = interfaceDesc.getUsbSubclass(); + // TODO shouldn't this switch on subtype? switch (subClass) { case AUDIO_AUDIOCONTROL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_AUDIOCONTROL"); + } return new UsbACAudioControlEndpoint(length, type, subClass); case AUDIO_AUDIOSTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_AUDIOSTREAMING"); + } return new UsbACAudioStreamEndpoint(length, type, subClass); case AUDIO_MIDISTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> AUDIO_MIDISTREAMING"); + } return new UsbACMidiEndpoint(length, type, subClass); default: diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java index 38c12a1f6c16..82fbfb89c498 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java @@ -100,8 +100,14 @@ public abstract class UsbACInterface extends UsbDescriptor { switch (subtype) { case ACI_HEADER: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_HEADER"); + } int acInterfaceSpec = stream.unpackUsbShort(); parser.setACInterfaceSpec(acInterfaceSpec); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACHeader(length, type, subtype, subClass, acInterfaceSpec); } else { @@ -111,7 +117,13 @@ public abstract class UsbACInterface extends UsbDescriptor { case ACI_INPUT_TERMINAL: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_INPUT_TERMINAL"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACInputTerminal(length, type, subtype, subClass); } else { @@ -121,7 +133,13 @@ public abstract class UsbACInterface extends UsbDescriptor { case ACI_OUTPUT_TERMINAL: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_OUTPUT_TERMINAL"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACOutputTerminal(length, type, subtype, subClass); } else { @@ -130,14 +148,26 @@ public abstract class UsbACInterface extends UsbDescriptor { } case ACI_SELECTOR_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_SELECTOR_UNIT"); + } return new UsbACSelectorUnit(length, type, subtype, subClass); case ACI_FEATURE_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_FEATURE_UNIT"); + } return new UsbACFeatureUnit(length, type, subtype, subClass); case ACI_MIXER_UNIT: { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> ACI_MIXER_UNIT"); + } int acInterfaceSpec = parser.getACInterfaceSpec(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " acInterfaceSpec:0x" + Integer.toHexString(acInterfaceSpec)); + } if (acInterfaceSpec == UsbDeviceDescriptor.USBSPEC_2_0) { return new Usb20ACMixerUnit(length, type, subtype, subClass); } else { @@ -215,14 +245,23 @@ public abstract class UsbACInterface extends UsbDescriptor { int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_AUDIOCONTROL"); + } return allocAudioControlDescriptor( parser, stream, length, type, subtype, subClass); case AUDIO_AUDIOSTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_AUDIOSTREAMING"); + } return allocAudioStreamingDescriptor( parser, stream, length, type, subtype, subClass); case AUDIO_MIDISTREAMING: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " AUDIO_MIDISTREAMING"); + } return allocMidiStreamingDescriptor(length, type, subtype, subClass); default: diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java index 639aa4e03849..3f2d8c8ef47c 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -30,7 +30,6 @@ import java.util.ArrayList; */ public final class UsbConfigDescriptor extends UsbDescriptor { private static final String TAG = "UsbConfigDescriptor"; - private static final boolean DEBUG = false; private int mTotalLength; // 2:2 Total length in bytes of data returned private byte mNumInterfaces; // 4:1 Number of Interfaces @@ -42,6 +41,8 @@ public final class UsbConfigDescriptor extends UsbDescriptor { // D4..0 Reserved, set to 0. private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + private boolean mBlockAudio; // leave it off for now. We be replace with a "Developer Option" + private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors = new ArrayList<UsbInterfaceDescriptor>(); @@ -78,21 +79,35 @@ public final class UsbConfigDescriptor extends UsbDescriptor { mInterfaceDescriptors.add(interfaceDesc); } + private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) { + return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO + && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING; + } + UsbConfiguration toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " toAndroid()"); } + + // NOTE - This code running in the server process. + //TODO (pmclean@) - remove this +// int pid = android.os.Process.myPid(); +// int uid = android.os.Process.myUid(); +// Log.d(TAG, " ---- pid:" + pid + " uid:" + uid); + String name = parser.getDescriptorString(mConfigIndex); UsbConfiguration config = new UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower); - UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()]; - if (DEBUG) { - Log.d(TAG, " " + mInterfaceDescriptors.size() + " interfaces."); - } - for (int index = 0; index < mInterfaceDescriptors.size(); index++) { - interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser); + + ArrayList<UsbInterface> filteredInterfaces = new ArrayList<UsbInterface>(); + for (UsbInterfaceDescriptor descriptor : mInterfaceDescriptors) { + if (!mBlockAudio || !isAudioInterface(descriptor)) { + filteredInterfaces.add(descriptor.toAndroid(parser)); + } } - config.setInterfaces(interfaces); + UsbInterface[] interfaceArray = new UsbInterface[0]; + interfaceArray = filteredInterfaces.toArray(interfaceArray); + config.setInterfaces(interfaceArray); return config; } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java index ff67667e848d..44422a2d4603 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java @@ -43,7 +43,7 @@ public abstract class UsbDescriptor implements Reporting { protected int mHierarchyLevel; protected final int mLength; // 0:1 bLength Number Size of the Descriptor in Bytes (18 bytes) - // we store this as an int because Java bytes are SIGNED. + // we store this as an int because Java bytes are SIGNED. protected final byte mType; // 1:1 bDescriptorType Constant Device Descriptor (0x01) private byte[] mRawData; @@ -52,11 +52,11 @@ public abstract class UsbDescriptor implements Reporting { private static byte[] sStringBuffer = new byte[SIZE_STRINGBUFFER]; // Status - public static final int STATUS_UNPARSED = 0; - public static final int STATUS_PARSED_OK = 1; - public static final int STATUS_PARSED_UNDERRUN = 2; - public static final int STATUS_PARSED_OVERRUN = 3; - public static final int STATUS_PARSE_EXCEPTION = 4; + public static final int STATUS_UNPARSED = 0; + public static final int STATUS_PARSED_OK = 1; + public static final int STATUS_PARSED_UNDERRUN = 2; + public static final int STATUS_PARSED_OVERRUN = 3; + public static final int STATUS_PARSE_EXCEPTION = 4; private int mStatus = STATUS_UNPARSED; @@ -78,53 +78,53 @@ public abstract class UsbDescriptor implements Reporting { public static final byte DESCRIPTORTYPE_HID = 0x21; // 33 public static final byte DESCRIPTORTYPE_REPORT = 0x22; // 34 public static final byte DESCRIPTORTYPE_PHYSICAL = 0x23; // 35 - public static final byte DESCRIPTORTYPE_AUDIO_INTERFACE = 0x24; // 36 - public static final byte DESCRIPTORTYPE_AUDIO_ENDPOINT = 0x25; // 37 + public static final byte DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE = 0x24; // 36 + public static final byte DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT = 0x25; // 37 public static final byte DESCRIPTORTYPE_HUB = 0x29; // 41 public static final byte DESCRIPTORTYPE_SUPERSPEED_HUB = 0x2A; // 42 public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48 // Class IDs - public static final int CLASSID_DEVICE = 0x00; - public static final int CLASSID_AUDIO = 0x01; - public static final int CLASSID_COM = 0x02; - public static final int CLASSID_HID = 0x03; + public static final int CLASSID_DEVICE = 0x00; + public static final int CLASSID_AUDIO = 0x01; + public static final int CLASSID_COM = 0x02; + public static final int CLASSID_HID = 0x03; // public static final int CLASSID_??? = 0x04; - public static final int CLASSID_PHYSICAL = 0x05; - public static final int CLASSID_IMAGE = 0x06; - public static final int CLASSID_PRINTER = 0x07; - public static final int CLASSID_STORAGE = 0x08; - public static final int CLASSID_HUB = 0x09; - public static final int CLASSID_CDC_CONTROL = 0x0A; - public static final int CLASSID_SMART_CARD = 0x0B; + public static final int CLASSID_PHYSICAL = 0x05; + public static final int CLASSID_IMAGE = 0x06; + public static final int CLASSID_PRINTER = 0x07; + public static final int CLASSID_STORAGE = 0x08; + public static final int CLASSID_HUB = 0x09; + public static final int CLASSID_CDC_CONTROL = 0x0A; + public static final int CLASSID_SMART_CARD = 0x0B; //public static final int CLASSID_??? = 0x0C; - public static final int CLASSID_SECURITY = 0x0D; - public static final int CLASSID_VIDEO = 0x0E; - public static final int CLASSID_HEALTHCARE = 0x0F; - public static final int CLASSID_AUDIOVIDEO = 0x10; - public static final int CLASSID_BILLBOARD = 0x11; - public static final int CLASSID_TYPECBRIDGE = 0x12; - public static final int CLASSID_DIAGNOSTIC = 0xDC; - public static final int CLASSID_WIRELESS = 0xE0; - public static final int CLASSID_MISC = 0xEF; - public static final int CLASSID_APPSPECIFIC = 0xFE; + public static final int CLASSID_SECURITY = 0x0D; + public static final int CLASSID_VIDEO = 0x0E; + public static final int CLASSID_HEALTHCARE = 0x0F; + public static final int CLASSID_AUDIOVIDEO = 0x10; + public static final int CLASSID_BILLBOARD = 0x11; + public static final int CLASSID_TYPECBRIDGE = 0x12; + public static final int CLASSID_DIAGNOSTIC = 0xDC; + public static final int CLASSID_WIRELESS = 0xE0; + public static final int CLASSID_MISC = 0xEF; + public static final int CLASSID_APPSPECIFIC = 0xFE; public static final int CLASSID_VENDSPECIFIC = 0xFF; // Audio Subclass codes - public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; - public static final int AUDIO_AUDIOCONTROL = 0x01; - public static final int AUDIO_AUDIOSTREAMING = 0x02; - public static final int AUDIO_MIDISTREAMING = 0x03; + public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; + public static final int AUDIO_AUDIOCONTROL = 0x01; + public static final int AUDIO_AUDIOSTREAMING = 0x02; + public static final int AUDIO_MIDISTREAMING = 0x03; // Request IDs - public static final int REQUEST_GET_STATUS = 0x00; - public static final int REQUEST_CLEAR_FEATURE = 0x01; - public static final int REQUEST_SET_FEATURE = 0x03; - public static final int REQUEST_GET_ADDRESS = 0x05; - public static final int REQUEST_GET_DESCRIPTOR = 0x06; - public static final int REQUEST_SET_DESCRIPTOR = 0x07; - public static final int REQUEST_GET_CONFIGURATION = 0x08; - public static final int REQUEST_SET_CONFIGURATION = 0x09; + public static final int REQUEST_GET_STATUS = 0x00; + public static final int REQUEST_CLEAR_FEATURE = 0x01; + public static final int REQUEST_SET_FEATURE = 0x03; + public static final int REQUEST_GET_ADDRESS = 0x05; + public static final int REQUEST_GET_DESCRIPTOR = 0x06; + public static final int REQUEST_SET_DESCRIPTOR = 0x07; + public static final int REQUEST_GET_CONFIGURATION = 0x08; + public static final int REQUEST_SET_CONFIGURATION = 0x09; // USB control transfer timeout public static final int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200; @@ -163,7 +163,6 @@ public abstract class UsbDescriptor implements Reporting { public int getOverUnderRunCount() { return mOverUnderRunCount; } - public String getStatusString() { return sStatusStrings[mStatus]; } @@ -278,4 +277,24 @@ public abstract class UsbDescriptor implements Reporting { + " Len: " + getLength(); canvas.writeParagraph(text, false); } + + /* + * Logging Helpers + */ + static String getDescriptorName(byte descriptorType, int descriptorLength) { + String name = UsbStrings.getDescriptorName(descriptorType); + if (name != null) { + return name; + } else { + return "Unknown Descriptor Type " + descriptorType + + " 0x" + Integer.toHexString(descriptorType) + + " length:" + descriptorLength; + } + } + + static void logDescriptorName(byte descriptorType, int descriptorLength) { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "----> " + getDescriptorName(descriptorType, descriptorLength)); + } + } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index c02110186d8d..43d5bf323abb 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -26,7 +26,7 @@ import java.util.ArrayList; */ public final class UsbDescriptorParser { private static final String TAG = "UsbDescriptorParser"; - private static final boolean DEBUG = false; + public static final boolean DEBUG = false; private final String mDeviceAddr; @@ -43,6 +43,11 @@ public final class UsbDescriptorParser { // Obtained from the first AudioClass Header descriptor. private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; + // The VideoClass spec implemented by the VideoClass Interfaces + // This may well be different than the overall USB Spec. + // Obtained from the first VidieoClass Header descriptor. + private int mVCInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; + /** * Connect this parser to an existing set of already parsed descriptors. * This is useful for reporting. @@ -90,6 +95,14 @@ public final class UsbDescriptorParser { return mACInterfacesSpec; } + public void setVCInterfaceSpec(int spec) { + mVCInterfacesSpec = spec; + } + + public int getVCInterfaceSpec() { + return mVCInterfacesSpec; + } + private class UsbDescriptorsStreamFormatException extends Exception { String mMessage; UsbDescriptorsStreamFormatException(String message) { @@ -115,6 +128,8 @@ public final class UsbDescriptorParser { int length = stream.getUnsignedByte(); byte type = stream.getByte(); + UsbDescriptor.logDescriptorName(type, length); + UsbDescriptor descriptor = null; switch (type) { /* @@ -174,14 +189,66 @@ public final class UsbDescriptorParser { break; /* - * Audio Class Specific + * Various Class Specific */ - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: - descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE: + if (mCurInterfaceDescriptor != null) { + switch (mCurInterfaceDescriptor.getUsbClass()) { + case UsbDescriptor.CLASSID_AUDIO: + descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); + break; + + case UsbDescriptor.CLASSID_VIDEO: + if (DEBUG) { + Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO"); + } + descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type); + break; + + case UsbDescriptor.CLASSID_AUDIOVIDEO: + if (DEBUG) { + Log.d(TAG, " UsbDescriptor.CLASSID_AUDIOVIDEO"); + } + break; + + default: + Log.w(TAG, " Unparsed Class-specific"); + break; + } + } break; - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: - descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT: + if (mCurInterfaceDescriptor != null) { + int subClass = mCurInterfaceDescriptor.getUsbClass(); + switch (subClass) { + case UsbDescriptor.CLASSID_AUDIO: + descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + break; + + case UsbDescriptor.CLASSID_VIDEO: { + Byte subtype = stream.getByte(); + if (DEBUG) { + Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x" + + Integer.toHexString(type)); + } + descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype); + } + break; + + case UsbDescriptor.CLASSID_AUDIOVIDEO: + if (DEBUG) { + Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO type:0x" + + Integer.toHexString(type)); + } + break; + + default: + Log.w(TAG, " Unparsed Class-specific Endpoint:0x" + + Integer.toHexString(subClass)); + break; + } + } break; default: @@ -190,8 +257,6 @@ public final class UsbDescriptorParser { if (descriptor == null) { // Unknown Descriptor - Log.i(TAG, "Unknown Descriptor len: " + length + " type:0x" - + Integer.toHexString(type)); descriptor = new UsbUnknown(length, type); } @@ -210,10 +275,6 @@ public final class UsbDescriptorParser { * @hide */ public void parseDescriptors(byte[] descriptors) { - if (DEBUG) { - Log.d(TAG, "parseDescriptors() - start"); - } - ByteStream stream = new ByteStream(descriptors); while (stream.available() > 0) { UsbDescriptor descriptor = null; @@ -231,9 +292,27 @@ public final class UsbDescriptorParser { // Clean up descriptor.postParse(stream); } catch (Exception ex) { - Log.e(TAG, "Exception parsing USB descriptors.", ex); + // Clean up, compute error status + descriptor.postParse(stream); - // Clean up + // Report + Log.w(TAG, "Exception parsing USB descriptors. type:0x" + descriptor.getType() + + " status:" + descriptor.getStatus()); + if (DEBUG) { + // Show full stack trace if debugging + Log.e(TAG, "Exception parsing USB descriptors.", ex); + } + StackTraceElement[] stackElems = ex.getStackTrace(); + if (stackElems.length > 0) { + Log.i(TAG, " class:" + stackElems[0].getClassName() + + " @ " + stackElems[0].getLineNumber()); + } + if (stackElems.length > 1) { + Log.i(TAG, " class:" + stackElems[1].getClassName() + + " @ " + stackElems[1].getLineNumber()); + } + + // Finish up descriptor.setStatus(UsbDescriptor.STATUS_PARSE_EXCEPTION); } finally { mDescriptors.add(descriptor); @@ -271,17 +350,17 @@ public final class UsbDescriptorParser { /** * @hide */ - public UsbDevice.Builder toAndroidUsbDevice() { + public UsbDevice.Builder toAndroidUsbDeviceBuilder() { if (mDeviceDescriptor == null) { Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor"); return null; } - UsbDevice.Builder device = mDeviceDescriptor.toAndroid(this); - if (device == null) { + UsbDevice.Builder builder = mDeviceDescriptor.toAndroid(this); + if (builder == null) { Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device"); } - return device; + return builder; } /** @@ -325,8 +404,8 @@ public final class UsbDescriptorParser { public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { - if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { - // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE + if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE) { + // ensure that this isn't an unrecognized DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE if (descriptor instanceof UsbACInterface) { UsbACInterface acDescriptor = (UsbACInterface) descriptor; if (acDescriptor.getSubtype() == subtype @@ -334,8 +413,8 @@ public final class UsbDescriptorParser { list.add(descriptor); } } else { - Log.w(TAG, "Unrecognized Audio Interface l: " + descriptor.getLength() - + " t:0x" + Integer.toHexString(descriptor.getType())); + Log.w(TAG, "Unrecognized Audio Interface len: " + descriptor.getLength() + + " type:0x" + Integer.toHexString(descriptor.getType())); } } } @@ -486,6 +565,61 @@ public final class UsbDescriptorParser { /** * @hide */ + public boolean hasAudioTerminal(int subType) { + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbACInterface) { + if (((UsbACInterface) descriptor).getSubclass() + == UsbDescriptor.AUDIO_AUDIOCONTROL + && ((UsbACInterface) descriptor).getSubtype() + == subType) { + return true; + } + } + } + return false; + } + + /** + * @hide + */ + public boolean hasAudioPlayback() { + return hasAudioTerminal(UsbACInterface.ACI_OUTPUT_TERMINAL); + } + + /** + * @hide + */ + public boolean hasAudioCapture() { + return hasAudioTerminal(UsbACInterface.ACI_INPUT_TERMINAL); + } + + /** + * @hide + */ + public boolean hasVideoCapture() { + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbVCInputTerminal) { + return true; + } + } + return false; + } + + /** + * @hide + */ + public boolean hasVideoPlayback() { + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbVCOutputTerminal) { + return true; + } + } + return false; + } + + /** + * @hide + */ public boolean hasHIDInterface() { ArrayList<UsbDescriptor> descriptors = getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java index f50b9cbc5e3d..b1cbbafb9ccd 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java @@ -31,7 +31,6 @@ import java.util.ArrayList; */ public final class UsbDeviceDescriptor extends UsbDescriptor { private static final String TAG = "UsbDeviceDescriptor"; - private static final boolean DEBUG = false; public static final int USBSPEC_1_0 = 0x0100; public static final int USBSPEC_1_1 = 0x0110; @@ -136,19 +135,19 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { * @hide */ public UsbDevice.Builder toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid()"); } String mfgName = getMfgString(parser); String prodName = getProductString(parser); - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " mfgName:" + mfgName + " prodName:" + prodName); } String versionString = getDeviceReleaseString(); String serialStr = getSerialString(parser); - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, " versionString:" + versionString + " serialStr:" + serialStr); } @@ -157,11 +156,12 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { for (int index = 0; index < mConfigDescriptors.size(); index++) { configs[index] = mConfigDescriptors.get(index).toAndroid(parser); } - UsbDevice.Builder device = new UsbDevice.Builder(parser.getDeviceAddr(), mVendorID, - mProductID, mDevClass, mDevSubClass, mProtocol, mfgName, prodName, versionString, - configs, serialStr); - return device; + return new UsbDevice.Builder(parser.getDeviceAddr(), mVendorID, + mProductID, mDevClass, mDevSubClass, mProtocol, mfgName, prodName, versionString, + configs, serialStr, parser.hasAudioPlayback(), parser.hasAudioCapture(), + parser.hasMIDIInterface(), + parser.hasVideoPlayback(), parser.hasVideoCapture()); } @Override diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 4da31ea469c3..4d0cfea98630 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -27,12 +27,11 @@ import com.android.server.usb.descriptors.report.ReportCanvas; */ public class UsbEndpointDescriptor extends UsbDescriptor { private static final String TAG = "UsbEndpointDescriptor"; - private static final boolean DEBUG = false; public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111; public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000; public static final int DIRECTION_OUTPUT = 0x0000; - public static final int DIRECTION_INPUT = (byte) 0x0080; + public static final int DIRECTION_INPUT = 0x0080; public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011; public static final int TRANSTYPE_CONTROL = 0x00; @@ -86,7 +85,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { } public int getEndpointAddress() { - return mEndpointAddress; + return mEndpointAddress & MASK_ENDPOINT_ADDRESS; } public int getAttributes() { @@ -109,8 +108,12 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mSyncAddress; } + public int getDirection() { + return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION; + } + /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() type:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE) + " sync:" + Integer.toHexString(mAttributes & MASK_ATTRIBS_SYNCTYPE) @@ -138,11 +141,9 @@ public class UsbEndpointDescriptor extends UsbDescriptor { canvas.openList(); - int address = getEndpointAddress(); canvas.writeListItem("Address: " - + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS) - + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION) - == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]")); + + ReportCanvas.getHexString(getEndpointAddress()) + + (getDirection() == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]")); int attributes = getAttributes(); canvas.openListItem(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 632e3dc500fa..64dbd971cc67 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -31,8 +31,6 @@ import java.util.ArrayList; */ public class UsbInterfaceDescriptor extends UsbDescriptor { private static final String TAG = "UsbInterfaceDescriptor"; - private static final boolean DEBUG = false; - protected int mInterfaceNumber; // 2:1 Number of Interface protected byte mAlternateSetting; // 3:1 Value used to select alternative setting protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface @@ -74,6 +72,19 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { return mNumEndpoints; } + /** + * @param index Index of desired UsbEndpointDescriptor. + * @return the UsbEndpointDescriptor descriptor at the specified index, or + * null if an invalid index. + */ + public UsbEndpointDescriptor getEndpointDescriptor(int index) { + if (index < 0 || index >= mEndpointDescriptors.size()) { + return null; + } + + return mEndpointDescriptors.get(index); + } + public int getUsbClass() { return mUsbClass; } @@ -95,7 +106,7 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { } UsbInterface toAndroid(UsbDescriptorParser parser) { - if (DEBUG) { + if (UsbDescriptorParser.DEBUG) { Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass) + " subclass:" + Integer.toHexString(mUsbSubclass) + " " + mEndpointDescriptors.size() + " endpoints."); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java new file mode 100644 index 000000000000..f9acecee6bcf --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +/** + * @hide + * A video class-specific Endpoint + * see USB_Video_Class_1.1.pdf - 3.10 VideoStreaming Endpoint Descriptors + */ +abstract class UsbVCEndpoint extends UsbDescriptor { + private static final String TAG = "UsbVCEndpoint"; + + + public static final byte VCEP_UNDEFINED = 0x00; + public static final byte VCEP_GENERAL = 0x01; + public static final byte VCEP_ENDPOINT = 0x02; + public static final byte VCEP_INTERRUPT = 0x03; + + UsbVCEndpoint(int length, byte type) { + super(length, type); + } + + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, + int length, byte type, byte subtype) { + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + + // TODO - create classes for each specific subtype + // (don't need it to answer if this device supports video + switch (subtype) { + case VCEP_UNDEFINED: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> VCEP_UNDEFINED"); + } + return null; + + case VCEP_GENERAL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> VCEP_GENERAL"); + } + return null; + + case VCEP_ENDPOINT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> VCEP_ENDPOINT"); + } + return null; + + case VCEP_INTERRUPT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, "---> VCEP_INTERRUPT"); + } + return null; + + default: + Log.w(TAG, "Unknown Video Class Endpoint id:0x" + Integer.toHexString(subtype)); + return null; + } + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java new file mode 100644 index 000000000000..3fc42241b550 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * A video class-specific Interface Header. + * see USB_Video_Class_1.1.pdf section 3.9.2 - Class-Specific VS Interface Descriptors + */ +public final class UsbVCHeader extends UsbVCHeaderInterface { + private static final String TAG = "UsbVCHeader"; + + // TODO Add data members for this descriptor's data + + public UsbVCHeader(int length, byte type, byte subtype, int spec) { + super(length, type, subtype, spec); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> parseRawDescriptors()"); + } + // TODO parse data members for this descriptor's data + return super.parseRawDescriptors(stream); + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + // TODO add reporting specific to this descriptor + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java new file mode 100644 index 000000000000..372509105ac3 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * A video class-specific Interface Header super class. + * see USB_Video_Class_1.1.pdf section 3.9.2 - Class-Specific VS Interface Descriptors + */ +public abstract class UsbVCHeaderInterface extends UsbVCInterface { + private static final String TAG = "UsbVCHeaderInterface"; + + protected int mVDCRelease; // Video Device Class Specification Release (BCD). + protected int mTotalLength; // Total number of bytes returned for the class-specific + // VideoControl interface descriptor. Includes the combined length + // of this descriptor header and all Unit and Terminal descriptors. + + public UsbVCHeaderInterface( + int length, byte type, byte subtype, int vdcRelease) { + super(length, type, subtype); + mVDCRelease = vdcRelease; + } + + public int getVDCRelease() { + return mVDCRelease; + } + + public int getTotalLength() { + return mTotalLength; + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + + canvas.openList(); + canvas.writeListItem("Release: " + ReportCanvas.getBCDString(getVDCRelease())); + canvas.writeListItem("Total Length: " + getTotalLength()); + canvas.closeList(); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java new file mode 100644 index 000000000000..df637950899b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * A video class-specific Input terminal interface. + * see USB_Video_Class_1.1.pdf section 3.7.2.1 Input Terminal Descriptor + */ +public final class UsbVCInputTerminal extends UsbVCInterface { + private static final String TAG = "UsbVCInputTerminal"; + + // TODO Define members to hold the data from this descriptor + public UsbVCInputTerminal(int length, byte type, byte subtype) { + super(length, type, subtype); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO Parse the data from this descriptor + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> parseRawDescriptors()"); + } + return super.parseRawDescriptors(stream); + } + + @Override + public void report(ReportCanvas canvas) { + // TODO Add reporting specific to this descriptor + super.report(canvas); + } +}; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java new file mode 100644 index 000000000000..46263dde5564 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +/** + * @hide + * A video class-specific Interface. + * see USB_Video_Class_1.1.pdf, section 3.7.2 + */ +public abstract class UsbVCInterface extends UsbDescriptor { + private static final String TAG = "UsbVCInterface"; + + // Class-specific Video Subtypes + public static final byte VCI_UNDEFINED = 0x00; + public static final byte VCI_VEADER = 0x01; + public static final byte VCI_INPUT_TERMINAL = 0x02; + public static final byte VCI_OUTPUT_TERMINAL = 0x03; + public static final byte VCI_SELECTOR_UNIT = 0x04; + public static final byte VCI_PROCESSING_UNIT = 0x05; + public static final byte VCI_EXTENSION_UNIT = 0x06; + + // See “Universal Serial Bus Device Class Definition for Video + protected final byte mSubtype; // 2:1 HEADER descriptor subtype + + public UsbVCInterface(int length, byte type, byte subtype) { + super(length, type); + mSubtype = subtype; + } + + /** + * Allocates an audio class interface subtype based on subtype and subclass. + */ + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, ByteStream stream, + int length, byte type) { + byte subtype = stream.getByte(); + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " Video Class-specific Interface subtype: " + subtype); + } + switch (subtype) { + // TODO - Create descriptor classes and parse these... + case VCI_UNDEFINED: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_UNDEFINED"); + } + break; + + case VCI_VEADER: + { + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_VEADER"); + } + int vcInterfaceSpec = stream.unpackUsbShort(); + parser.setVCInterfaceSpec(vcInterfaceSpec); + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " vcInterfaceSpec:0x" + Integer.toHexString(vcInterfaceSpec)); + } + return new UsbVCHeader(length, type, subtype, vcInterfaceSpec); + } + + case VCI_INPUT_TERMINAL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_INPUT_TERMINAL"); + } + return new UsbVCInputTerminal(length, type, subtype); + + case VCI_OUTPUT_TERMINAL: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_OUTPUT_TERMINAL"); + } + return new UsbVCOutputTerminal(length, type, subtype); + + case VCI_SELECTOR_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_SELECTOR_UNIT"); + } + return new UsbVCSelectorUnit(length, type, subtype); + + case VCI_PROCESSING_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_PROCESSING_UNIT"); + } + return new UsbVCProcessingUnit(length, type, subtype); + + case VCI_EXTENSION_UNIT: + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> VCI_EXTENSION_UNIT"); + } + break; + + default: + Log.w(TAG, "Unknown Video Class Interface subtype: 0x" + + Integer.toHexString(subtype)); + return null; + } + + return null; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java new file mode 100644 index 000000000000..4aa8ca22cc4e --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * A video class-specific Output Terminal Descriptor. + * see USB_Video_Class_1.1.pdf section 3.7.2.2 Output Terminal Descriptor + */ +public final class UsbVCOutputTerminal extends UsbVCInterface { + private static final String TAG = "UsbVCOutputTerminal"; + + // TODO Add members for the data in this descriptor + public UsbVCOutputTerminal(int length, byte type, byte subtype) { + super(length, type, subtype); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO Parse the data in this descriptor + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> parseRawDescriptors()"); + } + return super.parseRawDescriptors(stream); + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + // TODO Add reporting specific to this descriptor + } +}; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java new file mode 100644 index 000000000000..5ce842e82598 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * An video class-specific Processing Unit Interface. + * see USB_Video_Class_1.1.pdf section Table 3-8 Processing Unit Descriptor + */ +public final class UsbVCProcessingUnit extends UsbVCInterface { + private static final String TAG = "UsbVCProcessingUnit"; + + // TODO Add data members for this descriptor + + public UsbVCProcessingUnit(int length, byte type, byte subtype) { + super(length, type, subtype); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO Parse this descriptor + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> parseRawDescriptors()"); + } + return super.parseRawDescriptors(stream); + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + // TODO Add reporting specific to this descriptor + } +}; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java new file mode 100644 index 000000000000..8e9b0d886389 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 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.descriptors; + +import android.util.Log; + +import com.android.server.usb.descriptors.report.ReportCanvas; + +/** + * @hide + * An video class-specific Selector Unit Descriptor + * see USB_Video_Class_1.1.pdf section 3.7.2.4 + */ +public final class UsbVCSelectorUnit extends UsbVCInterface { + private static final String TAG = "UsbVCSelectorUnit"; + + // TODO Add data members for this descriptor + + public UsbVCSelectorUnit(int length, byte type, byte subtype) { + super(length, type, subtype); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO Parse this descriptor + if (UsbDescriptorParser.DEBUG) { + Log.d(TAG, " ---> parseRawDescriptors()"); + } + return super.parseRawDescriptors(stream); + } + + @Override + public void report(ReportCanvas canvas) { + super.report(canvas); + // TODO Add reporting specific to this descriptor + } +}; diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java index fb4576a6ee78..918ba2cc9249 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java @@ -56,9 +56,10 @@ public final class UsbStrings { sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HID, "HID"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_REPORT, "Report"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_PHYSICAL, "Physical"); - sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE, - "Audio Class Interface"); - sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT, "Audio Class Endpoint"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE, + "Class-specific Interface"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT, + "Class-specific Endpoint"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HUB, "Hub"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_SUPERSPEED_HUB, "Superspeed Hub"); sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT_COMPANION, diff --git a/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java b/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java index 1aa30fa94f42..72fa8977b675 100644 --- a/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java +++ b/services/usb/java/com/android/server/usb/descriptors/tree/UsbDescriptorsTree.java @@ -126,11 +126,13 @@ public final class UsbDescriptorsTree { // // Audio Class Descriptors // - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: - addACInterface((UsbACInterface) descriptor); + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE: + //TODO: This needs to be parsed out to Audio/Video... + // addACInterface((UsbACInterface) descriptor); break; - case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: + case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT: + //TODO: This needs to be parsed out to Audio/Video... break; } } |