Android USB_DEVICE_ATTACHED 持久权限

Android USB_DEVICE_ATTACHED persistent permission

如何让 Android 每次重新连接 USB 设备时不请求权限?我想让它记住 USB 设备的 "Use by default" 复选标记,这样我就不必每次都向同一设备授予权限。

我以编程方式检测 USB 设备 (android phones) 何时连接到我的主机设备 (android phone),以便我可以将它们切换到 AOA模式并将它们用作配件。基本上我有两个 android phones 和一个 OTG 电缆,我希望它们能够相互通信。

我有一个不断枚举连接的 USB 设备的线程:

UsbManager manager = (UsbManager) 
                   context.getSystemService(Context.USB_SERVICE);
while (!m_stopRequested) {
  boolean shouldNotify = false;
  HashMap<String, UsbDevice> deviceMap = m_usbManager.getDeviceList();
  for (Entry<String, UsbDevice> entry : deviceMap) {
    UsbDevice device = entry.getValue();
    if (m_usbManager.hasPermission(device)) {
      int pid = device.getProductId();
      if (device.getVendorId() == VID_GOOGLE(0x18D1) && (pid == ACCESSORY_PID(0x2D01) || pid == ACCESSORY_PID_ALT(0x2D00))) {
        switchDeviceToAOAMode(device);
      }
    } else {
      m_usbManager.requestPermission(device);
    }
  }
  Thread.sleep(1000);
}

我也注册了一个 BroadcastReceiver 来接收 USB_PERMISSION 意图:

private final class USBReceiver extends BroadcastReceiver {

    public void onReceive(Context context, Intent intent) {
        MCSLogger.log(TAG, "Received permission result!");

        String action = intent.getAction();
        UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

        if (ACTION_USB_PERMISSION.equals(action)) {
            boolean res = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
            MCSLogger.log(TAG, "permission action for dev=" + device + " received " + res);
            int pid = device.getProductId();
            if (res && device.getVendorId() == VID_GOOGLE(0x18D1) && (pid == ACCESSORY_PID(0x2D01) || pid == ACCESSORY_PID_ALT(0x2D00))) {
              connectAccessory()
            }
        }
    }
};

这是我切换到 AOA 模式的方式:

  private boolean switchDeviceToAOAMode(UsbDeviceConnection connection) {
        byte ioBuffer[] = new byte[2];
        int devVersion;
        int response;
    enter code here
        response = connection.controlTransfer(0xC0, 51, 0, 0, ioBuffer, 2, 0);

        if (response < 0) {
            MCSLogger.log(TAG, "Error starting transfer control " + response);
            return false;
        }

        devVersion = ioBuffer[1] << 8 | ioBuffer[0];

        // sometimes hangs on the next transfer :( //WIN32 libusb only
        // SystemClock.sleep(1000);

        byte manufacturer[] = m_manufacturer.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 0, manufacturer, manufacturer.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering manufacturer " + response);
            return false;
        }
        byte modelName[] = m_modelName.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 1, modelName, modelName.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering modelName " + response);
            return false;
        }
        byte description[] = m_description.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 2, description, description.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering description " + response);
            return false;
        }
        byte version[] = m_version.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 3, version, version.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering version " + response);
            return false;
        }
        byte uri[] = m_uri.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 4, uri, uri.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering uri " + response);
            return false;
        }
        byte serialNumber[] = m_serialNumber.getBytes();
        response = connection.controlTransfer(0x40, 52, 0, 5, serialNumber, serialNumber.length, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error transfering serialNumber " + response);
            return false;
        }

        MCSLogger.log(TAG, "Accessory Identification sent " + devVersion);

        response = connection.controlTransfer(0x40, 53, 0, 0, null, 0, 0);
        if (response < 0) {
            MCSLogger.log(TAG, "Error ending transfer control " + response);
            return false;
        }
        return true;
    }

在实现AOA的过程中,主要有两种方式获取USB数据传输的设备权限。

一种方法涉及手动枚举所有连接的设备,找到所需的设备,直接通过 UsbManager.requestPermission(Device device) 方法请求权限,并使用 BroadcastReceiver 处理生成的广播。这是您编写的解决方案。虽然功能正常且合规,但每次连接 USB 设备时都会提示用户许可;用户烦恼的潜在来源。

另一种方法要简单得多,并且允许 use-by-default 功能。它要求在 AndroidManifest.xml 中定义一个意图过滤器,如下所示:

<activity ...>
...
<intent-filter>
    <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>

<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
    android:resource="@xml/accessory_filter" />

连同一个名为 "accessory_filter" 的 xml 文件(只是一个建议,您可以随意命名)。这是一个示例 accessory_filter.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" /></resources>

Intent 过滤器会在设备连接时自动启动应用程序,并为用户提供选项,让用户可以将您的应用程序用作您正在使用的特定设备的默认应用程序。

此 link 提供了更多信息:https://developer.android.com/guide/topics/connectivity/usb/accessory#manifest-example

@Ender 提供的答案是正确的,但在 Android 平台(7+)的更高版本上,您还需要做一件事。

您需要确保已将 android:directBootAware="true" 添加到负责响应 USB_ACCESSORY_ATTACHED / USB_DEVICE_ATTACHED 权限的 activity 标签。

这是 activity 的有效清单部分:

    <activity android:name=".MainActivity"
              android:directBootAware="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>

        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                    android:resource="@xml/usb_device_filter" />
        </intent-filter>

    </activity>

来源:

https://github.com/dazza5000/USBPermissionTest/blob/master/app/src/main/AndroidManifest.xml

usb_device_filter.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-device vendor-id="2049" product-id="25"/>
</resources>

来源:

https://github.com/dazza5000/USBPermissionTest/blob/master/app/src/main/res/xml/usb_device_filter.xml

android:directBootAware="true"提示来自下面的link,非常感谢。

https://www.sdgsystems.com/post/android-usb-permissions

可在此处找到更多详细信息:

https://issuetracker.google.com/issues/77658221

完整的工作项目在这里:

https://github.com/dazza5000/USBPermissionTest

根访问权限

如果您有 root 访问权限,您可以创建文件并将其写入磁盘,然后重新启动设备,以便读取和设置默认权限。

这些是基本步骤:

private void grantUSBPermission() { UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();

for (UsbDevice usbDevice : deviceList.values()) {
    if (usbDevice.getManufacturerName() != null && usbDevice.getManufacturerName().equalsIgnoreCase(MANUFACTURER)) {
        Boolean hasPermission = usbManager.hasPermission(usbDevice);
        // Log if USB manager explicitly reports no permission.
        if (!hasPermission) {
            Log.i("DARRAN", "USB Manager reporting no permission to reader.");
            DeviceFilter deviceFilter = new DeviceFilter(usbDevice);
            writeSettingsFile(deviceFilter);
        }
    }
}

}

private void writeSettingsFile(DeviceFilter deviceFilter) {
    PermissionUtil.writeSettingsLocked(getApplicationContext(), deviceFilter);
    RootUtil.executeAsRoot(COMMAND_COPY_USB_FILE);
    RootUtil.executeAsRoot(COMMAND_CHOWN_USB_FILE);
    RootUtil.executeAsRoot("reboot");
}

命令:

public static final String COMMAND_COPY_USB_FILE = "cp /sdcard/Android/data/com.whereisdarran.setusbdefault/files/usb_device_manager.xml /data/system/users/0/usb_device_manager.xml";
public static final String COMMAND_CHOWN_USB_FILE = "chown system:system /data/system/users/0/usb_device_manager.xml";

可在此处找到完整的工作项目:

https://github.com/dazza5000/set-usb-default

此外,还有一篇具有更多上下文的博客文章:

http://whereisdarran.com/2019/12/wip-how-to-programmatically-set-your-app-as-the-default-app-for-a-usb-device-on-android-root-required/