【问题描述】
应用点击同意授予权限的弹窗授予了应用USB权限后,设备重启或usb设备拔插后还是需要重新授权

【问题分析】
USB设备的访问权限需要应用在运行时请求,并且为了确保用户可以知道应用访问了哪个USB设备,每个USB设备都会有一个对话框,用户必须批准该请求才能继续。这确保了用户对哪些应用程序可以访问其 USB 设备有直接的控制权。
为什么每次设备重启或usb设备拔插之后需要重新申请权限?
在 Android 系统中,动态申请的 USB 设备访问权限通常是临时的,意味着它们只在当前的应用中有效。一旦应用被完全关闭后,之前授予的权限就会失效。这样的设计主要是出于安全和隐私的考虑。

因此,每当应用程序设备重启或usb设备拔插后运行,它需要重新请求对 USB 设备的访问权限。这通常是通过使用 UsbManager 类和相关的 API 完成的。当应用检测到 USB 设备连接时,它可以通过发送一个 PendingIntent 来请求访问权限,系统会向用户显示一个对话框,让用户选择是否授权该权限。
这个流程确保了用户对哪些应用可以访问其 USB 设备有直接的控制,并且在设备重启或usb设备拔插时重新评估这些权限。这种机制有助于防止恶意应用在用户不知情的情况下长期访问 USB 设备。
应用申请USB权限的具体过程?
应用通过requestPermission方法申请USB权限
private void findDeviceAndRequestPermission() { HashMap deviceList = usbManager.getDeviceList(); UsbDevice device = null; // your logic to get the device if (device != null) { if (!usbManager.hasPermission(device)) { usbManager.requestPermission(device, permissionIntent); } } }
frameworks/base/libs/usb/src/com/android/future/usb/UsbManager.java
public void requestPermission(UsbAccessory accessory, PendingIntent pi) { try { mService.requestAccessoryPermission(new android.hardware.usb.UsbAccessory( accessory.getManufacturer(),accessory.getModel(), accessory.getDescription(), accessory.getVersion(), accessory.getUri(), accessory.getSerial()), mContext.getPackageName(), pi); } catch (RemoteException e) { Log.e(TAG, "RemoteException in requestPermission", e); } }
frameworks/base/services/usb/java/com/android/server/usb/UsbService.java
public void requestAccessoryPermission( UsbAccessory accessory, String packageName, PendingIntent pi) { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); final long token = Binder.clearCallingIdentity(); try { getPermissionsForUser(userId).requestPermission(accessory, packageName, pi, uid); } finally { Binder.restoreCallingIdentity(token); } }
getPermissionsForUser方法返回的是UsbUserPermissionManager对象,因此实际调用UsbUserPermissionManager类的requestPermission方法
frameworks/base/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
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); }
因为权限还没有授予,因此执行requestPermissionDialog方法
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); }
执行重载的requestPermissionDialog方法,最终跳转到UsbPermissionActivity
frameworks/base/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
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); // 跳转到UsbPermissionActivity 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); } }
UsbPermissionActivity 会创建一个对话框,当用户点击同意后,对话框会退出,并授予应用对应的USB权限
@Override public void onDestroy() { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); // send response via pending intent Intent intent = new Intent(); try { if (mDevice != null) { intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice); if (mPermissionGranted) { service.grantDevicePermission(mDevice, mUid); if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setDevicePackage(mDevice, mPackageName, userId); } } } if (mAccessory != null) { intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory); if (mPermissionGranted) { service.grantAccessoryPermission(mAccessory, mUid); if (mAlwaysUse != null && mAlwaysUse.isChecked()) { final int userId = UserHandle.getUserId(mUid); service.setAccessoryPackage(mAccessory, mPackageName, userId); } } } intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted); mPendingIntent.send(this, 0, intent); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "PendingIntent was cancelled"); } catch (RemoteException e) { Log.e(TAG, "IUsbService connection failed", e); } if (mDisconnectedReceiver != null) { unregisterReceiver(mDisconnectedReceiver); } super.onDestroy(); } public void onClick(DialogInterface dialog, int which) { if (which == AlertDialog.BUTTON_POSITIVE) { mPermissionGranted = true; } finish(); }
【修改对策】
根据上面分析,申请usb权限使Android系统出于安全原因,在应用被完全关闭、设备重新启动、USB设备断开后重连就需要重新申请USB权限;这其实不是一个BUG,但是因为客户应用不希望终端客户重复授权,觉得这样的操作会给用户带来困惑,因此还是需要更改。
根据上面追踪的代码,可知UsbPermissionActivity在onDestory()时,通过判断mPermissionGranted来决定是否授予权限
对策:针对客户应用修改,在onCreate阶段,通过筛选包名,设置mPermissionGranted为true,然后finish掉当前activity,直接默认授予对应应用USB权限
@Override public void onCreate(Bundle icicle) { super.onCreate(icicle); ... // Don't show the "always use" checkbox if the USB/Record warning is in effect if (!useRecordWarning && canBeDefault && (mDevice != null || mAccessory != null)) { // add "open when" checkbox LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE); ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse); if (mDevice == null) { mAlwaysUse.setText(getString(R.string.always_use_accessory, appName, mAccessory.getDescription())); } else { mAlwaysUse.setText(getString(R.string.always_use_device, appName, mDevice.getProductName())); } mAlwaysUse.setOnCheckedChangeListener(this); mClearDefaultHint = (TextView)ap.mView.findViewById( com.android.internal.R.id.clearDefaultHint); mClearDefaultHint.setVisibility(View.GONE); } // grant usb permission to app start if (!mPackageName.equals("app.package.name")){ setupAlert(); }else{ mPermissionGranted = true; finish(); } // grant usb permission to app end }