Search code examples
androidkotlinpermissionsuvc

Fail to get dialog box to request permission for UVC device


I am trying to develop an app to interface a phone with a specific UVC camera. I successfully detect the device when I plug it with the USB cable and am able to enumerate the different interfaces. My problem is that when I try to request permission to access this device in order to open the camera interface, no dialogbox appear to the user and the onReceive callback from my receiver is called immediately.

Here is my manifest where I added USB and camera permissions :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.experiment"
    android:versionCode="1"
    android:versionName="alpha">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.USB_PERMISSION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Experiment">
        <activity android:name=".MainActivity">
            <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" />
            </intent-filter>
            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>
</manifest>

and here is my MainActivity.kt :

package com.example.experiment

import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.*
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
private const val USB_SUBCLASS_CONTROL = 1
private const val USB_SUBCLASS_STREAMING = 2
private const val TAG = "EXPERIMENT"

class MainActivity : AppCompatActivity() {
    private lateinit var usbManager: UsbManager
    private lateinit var textLabel: TextView
    private lateinit var permissionIntent: PendingIntent
    private lateinit var device: UsbDevice
    private lateinit var devIf: UsbInterface
    private lateinit var conn: UsbDeviceConnection
    private lateinit var bytes: ByteArray
    private var open = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "**************************************************************")
        usbManager = getSystemService(Context.USB_SERVICE) as UsbManager

        permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
        val filter = IntentFilter(ACTION_USB_PERMISSION)
        registerReceiver(usbReceiver, filter)

        setContentView(R.layout.activity_main)
        textLabel = findViewById<TextView>(R.id.textlabel)
        val myButton: Button = findViewById(R.id.button_to_press)
        myButton.setOnClickListener {
            detectUSB()
        }
    }

    private val usbReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            Log.d(TAG,"onReceive called")
            if (ACTION_USB_PERMISSION == intent.action) {
                Log.d(TAG, "USB permission action")
                synchronized(this) {
                    device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                    Log.d(TAG, "got device")
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        device.apply {
                            //call method to set up device communication
                            Log.d(TAG, "trying to open device")
                            conn = usbManager.openDevice(device)
                            open = true
                        }
                    } else {
                        Log.d(TAG, "permission denied for device $device")
                    }
                }
            }
        }
    }

    private fun detectUSB() {
        val deviceList: HashMap<String, UsbDevice> = usbManager.deviceList
        val deviceIterator: Iterator<UsbDevice> = deviceList.values.iterator()
        var i = ""
        while (deviceIterator.hasNext()) {
            val device = deviceIterator.next()
            if (device.vendorId == 0xABCD && device.productId == 0x0123) {
                if (!usbManager.hasPermission(device)) {
                    Log.d(TAG, "request sent")
                    usbManager.requestPermission(device, permissionIntent)
                } else {
                    Log.d(TAG, "got permission to use device")
                    open = true
                }
                if (!open) {
                    i += "waiting for device permission"
                } else {
                    if (!conn.releaseInterface(devIf))
                    {
                        i += """releaseInterface failed""".trimIndent()
                    }
                    conn.close()
                    i += """
device closed""".trimIndent()
                    open = false
                }
            }
            i += """
            DeviceID: ${device.deviceId}
            DeviceName: ${device.deviceName}
            DeviceClass: ${device.deviceClass} - ${translateDeviceClass(device.deviceClass)}
            DeviceSubClass: ${device.deviceSubclass}
            VendorID: ${device.vendorId}
            ProductID: ${device.productId}
            InterfaceCount: ${device.interfaceCount}
            """
        }
        textLabel.text = i
    }

    private fun translateDeviceClass(deviceClass: Int): String {
        return when (deviceClass) {
            UsbConstants.USB_CLASS_APP_SPEC -> "Application specific USB class"
            UsbConstants.USB_CLASS_AUDIO -> "USB class for audio devices"
            UsbConstants.USB_CLASS_CDC_DATA -> "USB class for CDC devices (communications device class)"
            UsbConstants.USB_CLASS_COMM -> "USB class for communication devices"
            UsbConstants.USB_CLASS_CONTENT_SEC -> "USB class for content security devices"
            UsbConstants.USB_CLASS_CSCID -> "USB class for content smart card devices"
            UsbConstants.USB_CLASS_HID -> "USB class for human interface devices (for example, mice and keyboards)"
            UsbConstants.USB_CLASS_HUB -> "USB class for USB hubs"
            UsbConstants.USB_CLASS_MASS_STORAGE -> "USB class for mass storage devices"
            UsbConstants.USB_CLASS_MISC -> "USB class for wireless miscellaneous devices"
            UsbConstants.USB_CLASS_PER_INTERFACE -> "USB class indicating that the class is determined on a per-interface basis"
            UsbConstants.USB_CLASS_PHYSICA -> "USB class for physical devices"
            UsbConstants.USB_CLASS_PRINTER -> "USB class for printers"
            UsbConstants.USB_CLASS_STILL_IMAGE -> "USB class for still image devices (digital cameras)"
            UsbConstants.USB_CLASS_VENDOR_SPEC -> "Vendor specific USB class"
            UsbConstants.USB_CLASS_VIDEO -> "USB class for video devices"
            UsbConstants.USB_CLASS_WIRELESS_CONTROLLER -> "USB class for wireless controller devices"
            else -> "Unknown USB class!"
        }
    }
}

I read on https://developer.android.com/reference/android/hardware/usb/UsbManager#requestPermission(android.hardware.usb.UsbDevice,%20android.app.PendingIntent) that for UVC devices I needs to have camera permission which I added but I still don't get the permission dialogbox and am unable to open my device. What am I missing here ? How can I get the permission to access my device ? If I use the same code with any other USB device not tagged as UVC I get the dialog box every time.

Thanks for your help !


Solution

  • I found the reason for the problem : as stated here, Android 10 broke the UVC support during development and such devices cannot be accessed. This has been fixed on Pixels phone but not on most devices such as mine (Huawei P20 pro).

    To fix the issue, it is necessary to change the target sdk version to version 27. In Android Studio, this can be done by opening the Modules settings of your project and changing the Target SDK Version in the Default Config tab. You do not need to change the Compile Sdk Version. Be aware that choosing such sdk version will prevent you from publishing your app to the android store.