Last active
January 5, 2025 18:40
-
-
Save bj0/cf32761740029b4caeef3289f4d914a6 to your computer and use it in GitHub Desktop.
Requesting permission to open UsbDevice in Kotlin using coroutines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.my.app | |
import android.app.PendingIntent | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.content.IntentFilter | |
import android.hardware.usb.UsbDevice | |
import android.hardware.usb.UsbManager | |
import kotlinx.coroutines.experimental.CompletableDeferred | |
import kotlinx.coroutines.experimental.channels.Channel | |
import kotlinx.coroutines.experimental.runBlocking | |
/** | |
* action to identify our requests | |
*/ | |
private const val ACTION = "com.my.app.USB_PERMISSION" | |
// buffer size of one so the first send() queues instead of blocks | |
private val queue = Channel<CompletableDeferred<Boolean>>(1) | |
/** | |
* request permission for this device from the [UsbManager] | |
*/ | |
private fun Context.sendPermissionRequest(device: UsbDevice) { | |
usbManager.requestPermission(device, | |
PendingIntent.getBroadcast(this, 0, Intent(ACTION), 0)) | |
} | |
/** | |
* receive responses to permission requests | |
*/ | |
private val receiver = object : BroadcastReceiver() { | |
var isRegistered = false | |
private set | |
fun register(context: Context): Intent? = synchronized(this) { | |
return try { | |
if (!isRegistered) | |
context.registerReceiver(this, IntentFilter(ACTION)) | |
else | |
null | |
} finally { | |
isRegistered = true | |
} | |
} | |
fun unregister(context: Context) = synchronized(this) { | |
if (isRegistered) | |
try { | |
context.unregisterReceiver(this) | |
} finally { | |
isRegistered = false | |
} | |
} | |
override fun onReceive(context: Context?, intent: Intent?) { | |
if (intent?.action == ACTION) { | |
val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) | |
runBlocking { | |
// the queue should never be empty at this point... | |
val result = queue.poll() ?: return@runBlocking | |
// send result | |
result.complete(device != null && context?.usbManager?.hasPermission(device) == true) | |
} | |
} | |
} | |
} | |
/** | |
* register to receive responses to permission requests | |
*/ | |
fun Context.registerForUsbPermissions() = receiver.register(this) | |
/** | |
* unregister to no longer receive responses to permission requests | |
*/ | |
fun Context.unregisterForUsbPermissions() = receiver.unregister(this) | |
/** | |
* request permission for a device. returns true if permission is granted | |
*/ | |
suspend fun Context.requestPermission(device: UsbDevice, autoRegister: Boolean = true) = when { | |
usbManager.hasPermission(device) -> true | |
!receiver.isRegistered && !autoRegister -> false // throw if not registered? | |
else -> { | |
val result = CompletableDeferred<Boolean>() | |
// post a request in the queue so we receive the result. this will suspend if there's | |
// already a request in the queue (keeps the order correct) | |
queue.send(result) | |
// auto-register if not already registered | |
if (!receiver.isRegistered) registerForUsbPermissions() | |
sendPermissionRequest(device) | |
// wait for result | |
result.await() | |
} | |
} | |
Hello! You can replace the string
17) private const val ACTION = "com.my.app.USB_PERMISSION"
with
17) private const val ACTION = BuildConfig.APPLICATION_ID + "USB_PERMISSION"
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, could you add a
.kt
suffix to the file name so we get syntax highlighting?