Android 11 默认授予第三方应用USB权限

【问题描述】

应用点击同意授予权限的弹窗授予了应用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<String, UsbDevice> 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
}

  • 46
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值