Android 7.1 允许应用访问该USB设备弹窗

前言

在这里插入图片描述
本文基于: RK3128 + Android7.1 进行开发

Android的USB开发, 经常碰到权限的问题, 比如, 本文所提及的一类弹窗: 允许应用”XXX应用”访问该USB设备吗?
在RK3128 android 7.1的平台上, 碰到的问题是, 勾选了: 默认情况下使用该USB设备 后, 每次拔插USB 设备, 弹窗还是会出现

在这里插入图片描述

问题分析

  • AndroidManifest.xml 设备监听, 可选, 用于监听设备的拔插状态, 当指定VID/PID的设备接入是, 会自动调起MyUsbActivity
    情况1: 未设置为默认时, 系统会通过权限窗口, 提示赋予权限, 确定后启动Activity
    情况2: 已经设置默认时, 系统直接调起Activity, 跳过权限申请窗口

            <activity android:name=".MyUsbActivity"
                android:process=":usbTest">
                <intent-filter>
                    <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
                </intent-filter>
    
                <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                    android:resource="@xml/usb_device_filter"/>
            </activity>
    

    usb_device_filter.xml, 需要监听的设备列表

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:android="http://schemas.android.com/apk/res/android">
        <usb-device vendor-id="9595" product-id="12305" class="3" subclass="0" protocol="0"/>
        <usb-device vendor-id="1137" product-id="28675" />
    </resources>
    
  • App 申请USB设备权限的代码:

    final String USB_ACTION = "action_to_request_usb_device_permission";
    UsbManager usbMgr;
    UsbDevice dev;
    Context ctx;
    
    //1. 记得先注册广播接收, 在获得设备访问权限后会有广播回来
    private BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if(USB_ACTION.equals(intent.getAction())){
                    //可以开始连接USB设备
                }
            }
        };
    IntentFilter filter = new IntentFilter(USB_ACTION);
    ctx.registerReceiver(receiver, filter);
    
    //2. 检查是否有权限
    usbMgr.hasPermission(dev);
    
    //3. 若无权限, 获取到UsbDevice后,发送请求
    PendingIntent pi = PendingIntent.getBroadcast(ctx, 0, new Intent(USB_ACTION), 0);
    usbMgr.requestPermission(dev, pi);
    
    

  • 有用的命令: dumpsys usb

    • 输出结果, 重点关注两点:

      1. USB 设备的节点: /dev/bus/usb/001/010

      2. Device permissions: 获得权限的设备列表

        设备节点有权限的应用的UID
        /dev/bus/usb/001/01010036

      以下LOG信息, 是在连接了USB设备, 并且APP已经获取了权限

      USB Manager State:
        USB Device State:
          mCurrentFunctions: adb
          mCurrentFunctionsApplied: true
          mConnected: true
          mConfigured: true
          mUsbDataUnlocked: true
          mCurrentAccessory: null
          mHostConnected: false
          mSourcePower: false
          mSinkPower: false
          mUsbCharging: false
          Kernel state: CONFIGURED
          Kernel function list: ffs
        USB Host State:
          /dev/bus/usb/001/010: UsbDevice[mName=/dev/bus/usb/001/010,mVendorId=1137,mProductId=28675,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=USB,mProductName=Smart Card Reader,mVersion=1.16,mSerialNumber=201307262013728,mConfigurations=[
        UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=75,mInterfaces=[
        UsbInterface[mId=0,mAlternateSetting=0,mName=HID Iterface,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
        UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=64,mInterval=1]]
        UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=1,mEndpoints=[
        UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=8,mInterval=1]]]]
        USB Port State:
          <no ports>
        USB Audio Devices:
        USB MIDI Devices:
        Settings for user 0:
          Device permissions:
            /dev/bus/usb/001/010: 10036
      Accessory permissions:
          Device preferences:
            DeviceFilter[mVendorId=1137,mProductId=28675,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=USB,mProductName=Smart Card Reader,mSerialNumber=201307262013728]: com.your.packagename
          Accessory preferences:
      
  • frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

        //判断是否有权限
        @Override
        public boolean hasDevicePermission(UsbDevice device) {
            final int userId = UserHandle.getCallingUserId();
            return getSettingsForUser(userId).hasPermission(device);
        }
    
        private UsbSettingsManager getSettingsForUser(int userId) {
            synchronized (mLock) {
                UsbSettingsManager settings = mSettingsByUser.get(userId);
                if (settings == null) {
                    settings = new UsbSettingsManager(mContext, new UserHandle(userId));
                    mSettingsByUser.put(userId, settings);
                }
                return settings;
            }
        }
    
  • frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java

        public boolean hasPermission(UsbDevice device) {
            synchronized (mLock) {
                int uid = Binder.getCallingUid();
                if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                    return true;
                }
                SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
                if (uidList == null) {
                    return false;
                }
                return uidList.get(uid);
            }
        }
    

    mDisablePermissionDialogs 的值来自: frameworks/base/core/res/res/values/config.xml

    <bool name="config_disableUsbPermissionDialogs">false</bool>
    

    顾名思义, 如果吧这个值改为true, USB权限的弹窗将不再出现, 应用也不再需要申请权限 [解决方案 1]

    mDevicePermissionMap 中保存了设备的权限映射集合, 在申请成功后, grantDevicePermission 会将授权的设备和应用写入集合中, 而在USB设备断开后, 函数deviceDetached 会清掉对应的授权信息, 导致应用再次获取权限失败, 这就是问题的根源

  • 开始申请USB设备权限

    • frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

          @Override
          public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
              final int userId = UserHandle.getCallingUserId();
              getSettingsForUser(userId).requestPermission(device, packageName, pi);
          }
      
    • frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java

          private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
              final int uid = Binder.getCallingUid();
      
              // compare uid with packageName to foil apps pretending to be someone else
              try {
                  ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
                  if (aInfo.uid != uid) {
                      throw new IllegalArgumentException("package " + packageName +
                              " does not match caller's uid " + uid);
                  }
              } catch (PackageManager.NameNotFoundException e) {
                  throw new IllegalArgumentException("package " + packageName + " not found");
              }
      
              long identity = Binder.clearCallingIdentity();
              intent.setClassName("com.android.systemui",
                      "com.android.systemui.usb.UsbPermissionActivity");
              intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
              intent.putExtra(Intent.EXTRA_INTENT, pi);
              intent.putExtra("package", packageName);
              intent.putExtra(Intent.EXTRA_UID, uid);
              try {
                  mUserContext.startActivityAsUser(intent, mUser);
              } catch (ActivityNotFoundException e) {
                  Slog.e(TAG, "unable to start UsbPermissionActivity");
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      
    • 转到SystemUI
      frameworks/base/packages/SystemUI/AndroidManifest.xml

      <activity android:name=".usb.UsbPermissionActivity"
                  android:exported="true"
                  android:permission="android.permission.MANAGE_USB"
                  android:theme="@style/Theme.SystemUI.Dialog.Alert"
                  android:finishOnCloseSystemDialogs="true"
                  android:excludeFromRecents="true">
              </activity>
      
    • frameworks/base/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java

          @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.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.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();
          }
      
  • 两个主要的函数:
    frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java

    • grantAccessoryPermission: 更新 mDevicePermissionMap

          public void grantDevicePermission(UsbDevice device, int uid) {
              synchronized (mLock) {
                  String deviceName = device.getDeviceName();
                  SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
                  if (uidList == null) {
                      uidList = new SparseBooleanArray(1);
                      mDevicePermissionMap.put(deviceName, uidList);
                  }
                  uidList.put(uid, true);
              }
          }
      
    • setDevicePackage: writeSettingsLocked 将会把默认包名和设备绑定信息写入文件.

          public void setDevicePackage(UsbDevice device, String packageName) {
              DeviceFilter filter = new DeviceFilter(device);
              boolean changed = false;
              synchronized (mLock) {
                  if (packageName == null) {
                      changed = (mDevicePreferenceMap.remove(filter) != null);
                  } else {
                      changed = !packageName.equals(mDevicePreferenceMap.get(filter));
                      if (changed) {
                          mDevicePreferenceMap.put(filter, packageName);
                      }
                  }
                  if (changed) {
                      writeSettingsLocked();
                  }
              }
          }
          private void writeSettingsLocked() {
              if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
      
              FileOutputStream fos = null;
              try {
                  fos = mSettingsFile.startWrite();
      
                  FastXmlSerializer serializer = new FastXmlSerializer();
                  serializer.setOutput(fos, StandardCharsets.UTF_8.name());
                  serializer.startDocument(null, true);
                  serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
                  serializer.startTag(null, "settings");
      
                  for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
                      serializer.startTag(null, "preference");
                      serializer.attribute(null, "package", mDevicePreferenceMap.get(filter));
                      filter.write(serializer);
                      serializer.endTag(null, "preference");
                  }
      
                  for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
                      serializer.startTag(null, "preference");
                      serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
                      filter.write(serializer);
                      serializer.endTag(null, "preference");
                  }
      
                  serializer.endTag(null, "settings");
                  serializer.endDocument();
      
                  mSettingsFile.finishWrite(fos);
              } catch (IOException e) {
                  Slog.e(TAG, "Failed to write settings", e);
                  if (fos != null) {
                      mSettingsFile.failWrite(fos);
                  }
              }
          }
      
  • 其中 mSettingsFile = new AtomicFile(new File( Environment.getUserSystemDirectory(user.getIdentifier()), “usb_device_manager.xml”));
    对应的系统重的目录: /data/system/users/0/usb_device_manager.xml

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <settings>
        <preference package="com.your.packagename">
            <usb-device vendor-id="1137" product-id="28675" class="0" subclass="0" protocol="0" manufacturer-name="USB" product-name="Smart Card Reader" serial-number="201307262013728" />
        </preference>
    </settings>
    

小结: 常规的流程, 到这里已经走完. 要解决勾默认弹窗后, 还会再次出现弹窗的问题, 需要做的是:

  1. APP 中增加USB设备清单, 即前面提及的文件: usb_device_filter.xml
  2. 在App 的AndroidManifest.xml 中添加Activity, 以便系统服务在检测到指定的USB设备接入后, 正常调起并正常赋予设备权限.
    在完成了这两点后, 需要在第一次调起权限窗口时, 勾选默认并确认授权, 后续设备接入都不会再弹窗.

frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java

设备接入
     //设备接入
     public void deviceAttached(UsbDevice device) {
         final Intent intent = createDeviceAttachedIntent(device);
 
         // Send broadcast to running activity with registered intent
         mUserContext.sendBroadcast(intent);
 
         if (MtpNotificationManager.shouldShowNotification(mPackageManager, device)) {
             // Show notification if the device is MTP storage.
             mMtpNotificationManager.showNotification(device);
         } else {
             resolveActivity(intent, device);
         }
     }
查找默认的Activity
     private void resolveActivity(Intent intent, UsbDevice device) {
         ArrayList<ResolveInfo> matches;
         String defaultPackage;
         synchronized (mLock) {
             matches = getDeviceMatchesLocked(device, intent);
             // Launch our default activity directly, if we have one.
             // Otherwise we will start the UsbResolverActivity to allow the user to choose.
             defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
         }
 
         // Start activity with registered intent
         resolveActivity(intent, matches, defaultPackage, device, null);
     }
 
     private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
             String defaultPackage, UsbDevice device, UsbAccessory accessory) {
         int count = matches.size();
 
         // don't show the resolver activity if there are no choices available
         if (count == 0) {
             if (accessory != null) {
                 String uri = accessory.getUri();
                 if (uri != null && uri.length() > 0) {
                     // display URI to user
                     // start UsbResolverActivity so user can choose an activity
                     Intent dialogIntent = new Intent();
                     dialogIntent.setClassName("com.android.systemui",
                             "com.android.systemui.usb.UsbAccessoryUriActivity");
                     dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
                     dialogIntent.putExtra("uri", uri);
                     try {
                         mUserContext.startActivityAsUser(dialogIntent, mUser);
                     } catch (ActivityNotFoundException e) {
                         Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
                     }
                 }
             }
 
             // do nothing
             return;
         }
 
         ResolveInfo defaultRI = null;
         if (count == 1 && defaultPackage == null) {
             // Check to see if our single choice is on the system partition.
             // If so, treat it as our default without calling UsbResolverActivity
             ResolveInfo rInfo = matches.get(0);
             if (rInfo.activityInfo != null &&
                     rInfo.activityInfo.applicationInfo != null &&
                     (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                 defaultRI = rInfo;
             }
 
             if (mDisablePermissionDialogs) {
                 // bypass dialog and launch the only matching activity
                 rInfo = matches.get(0);
                 if (rInfo.activityInfo != null) {
                     defaultPackage = rInfo.activityInfo.packageName;
                 }
             }
         }
 
         if (defaultRI == null && defaultPackage != null) {
             // look for default activity
             for (int i = 0; i < count; i++) {
                 ResolveInfo rInfo = matches.get(i);
                 if (rInfo.activityInfo != null &&
                         defaultPackage.equals(rInfo.activityInfo.packageName)) {
                     defaultRI = rInfo;
                     break;
                 }
             }
         }
 
         if (defaultRI != null) {
             // grant permission for default activity
             if (device != null) {
                 grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
             } else if (accessory != null) {
                 grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
             }
 
             // start default activity directly
             try {
                 intent.setComponent(
                         new ComponentName(defaultRI.activityInfo.packageName,
                                 defaultRI.activityInfo.name));
                 mUserContext.startActivityAsUser(intent, mUser);
             } catch (ActivityNotFoundException e) {
                 Slog.e(TAG, "startActivity failed", e);
             }
         } else {
             Intent resolverIntent = new Intent();
             resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
             if (count == 1) {
                 // start UsbConfirmActivity if there is only one choice
                 resolverIntent.setClassName("com.android.systemui",
                         "com.android.systemui.usb.UsbConfirmActivity");
                 resolverIntent.putExtra("rinfo", matches.get(0));
 
                 if (device != null) {
                     resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
                 } else {
                     resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
                 }
             } else {
                 // start UsbResolverActivity so user can choose an activity
                 resolverIntent.setClassName("com.android.systemui",
                         "com.android.systemui.usb.UsbResolverActivity");
                 resolverIntent.putParcelableArrayListExtra("rlist", matches);
                 resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
             }
             try {
                 mUserContext.startActivityAsUser(resolverIntent, mUser);
             } catch (ActivityNotFoundException e) {
                 Slog.e(TAG, "unable to start activity " + resolverIntent);
             }
         }
     }
赋予权限
    public void grantDevicePermission(UsbDevice device, int uid) {
         synchronized (mLock) {
             String deviceName = device.getDeviceName();
             SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
             if (uidList == null) {
                 uidList = new SparseBooleanArray(1);
                 mDevicePermissionMap.put(deviceName, uidList);
             }
             uidList.put(uid, true);
         }
     }

[解决方案 3] 增加系统代码, 通过读取已经保存的设备和包名的绑定信息来自动赋予应用USB设备权限

git diff frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java
diff --git a/frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index de9ede397c..5e72d683a3 100644
--- a/frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/frameworks/base/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -743,9 +743,30 @@ class UsbSettingsManager {
             mMtpNotificationManager.showNotification(device);
         } else {
             resolveActivity(intent, device);
+                       //AnsonCode
+                       grantPackagePermission(device, null);
         }
     }
 
+       //AnsonCode
+       void d(String s){android.util.Log.d("UsbSettingsManager", "ALog." + s);}
+       void grantPackagePermission(UsbDevice device, UsbAccessory accessory){
+               //d("grantPackagePermission");
+               String defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
+               if(defaultPackage != null){
+                       try {
+                               ApplicationInfo aInfo = mPackageManager.getApplicationInfo(defaultPackage, 0);
+                               if(aInfo != null){
+                                       if(device != null)grantDevicePermission(device, aInfo.uid);
+                                       if(accessory != null)grantAccessoryPermission(accessory, aInfo.uid);
+                                       //d("grantPackagePermission " + defaultPackage + " uid=" + aInfo.uid);
+                               }
+                       } catch (PackageManager.NameNotFoundException e) {
+                               d("NameNotFoundException:" + defaultPackage);
+                       }
+               }
+       }
+
     private void resolveActivity(Intent intent, UsbDevice device) {
         ArrayList<ResolveInfo> matches;
         String defaultPackage;
@@ -787,6 +808,8 @@ class UsbSettingsManager {
         }
 
         resolveActivity(intent, matches, defaultPackage, null, accessory);
+               //AnsonCode
+               if(accessory != null) grantPackagePermission(null, accessory);
     }

总结

解决USB授权的弹窗方案有3种:

  1. 应用通过一系列的配置, 增加指定设备的过滤监听, 实现应用自启并获得权限
  2. 修改系统配置, 关闭USB权限弹窗
  3. 修改系统Framework中的USB服务, 增加通过默认绑定包名自动赋权.
  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值