Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android USB_DEVICE_ATTACHED persistent permission

How can I make Android not request for permission each time I reconnect a USB device? I want to make it to remember "Use by default" checkmark for the USB devices so that I don't have to give permission every time to the same device.

I programatically detect when USB devices (android phones) are attached to my host device (android phone) so that I can switch them to AOA mode and use them as accessories. Basically I have two android phones and an OTG cable and I want them to communicate between eachother.

I have a thread which constantly enumerates the attached USB devices:

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);
}

I also have a BroadcastReceiver registered to receive USB_PERMISSION intents:

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()
            }
        }
    }
};

This is how I switch to AOA mode:

  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;
    }
like image 809
Ivan Dortulov Avatar asked Oct 16 '16 20:10

Ivan Dortulov


People also ask

How do I give permission to a USB device in Android?

Android gave our app temporary permission to access the USB device by updating UsbUserSettingsManager.mDevicePermissionMap.Android saved our app’s package name and the USB device’s information in the file “/data/system/users/0/usb_device_manager.xml”.

What happens when I connect a USB device to Android?

When users connect USB devices to an Android-powered device, the Android system can determine whether your application is interested in the connected device. If so, you can set up communication with the device if desired.

How to filter USB devices with specified attributes in Android?

... ... In this case, the following resource file should be saved in res/xml/device_filter.xml and specifies that any USB device with the specified attributes should be filtered: When users connect USB devices to an Android-powered device, the Android system can determine whether your application is interested in the connected device.

How do I know if a USB device has been granted permission?

When the USB device is plugged in, if it hasn't already been granted permission then the USB permission box will popup. If the user clicks OK without checking the remember box, then the box will popup again the next time the device is connected.


2 Answers

The answer provided by @Ender is correct, but there is one more thing you need to do on later versions of the Android Platform (7+).

You need to make sure that you have android:directBootAware="true" added to the activity tag that is responsible for responding to the USB_ACCESSORY_ATTACHED / USB_DEVICE_ATTACHED permissions.

Here is a valid manifest section for the 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>

Source:

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>

Source:

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

The android:directBootAware="true" hint comes from the link below and I am very thankful for it.

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

More details can be found here:

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

A full working project is here:

https://github.com/dazza5000/USBPermissionTest

Root Access

If you have root access, you can create the file and write it to disk and then reboot the device so that the default permission is read and set.

These are the basic steps:

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");
}

Commands:

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";

A full working project can be found here:

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

Also, a blog article with a little more context:

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

like image 172
dazza5000 Avatar answered Sep 22 '22 17:09

dazza5000


In implementing AOA, there are two main ways to obtain device permission for USB data transfers.

One approach involves manually enumerating all connected devices, finding the desired device, directly requesting permission via the UsbManager.requestPermission(Device device) method, and handling the resulting broadcast with a BroadcastReceiver. This is the solution you've written. While functional and compliant, it prompts the user for permission every time a USB device is connected; a potential source of annoyance for the user.

The other approach is far simpler and allows for use-by-default functionality. It requires that an intent filter be defined in AndroidManifest.xml like so:

<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" />

Along with an xml file named "accessory_filter"(just a suggestion, you can name it whatever you want). Here's a sample accessory_filter.xml file:

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

The intent filter will automatically fire up the application in the event of a device connection and presents the user with the option to use your app as the default application for the specific device you are working with.

This link provides more information: https://developer.android.com/guide/topics/connectivity/usb/accessory#manifest-example

like image 33
Ender Avatar answered Sep 22 '22 17:09

Ender