From 07fd16f2bd9db7a568b08507e5454407f1b1d84f Mon Sep 17 00:00:00 2001 From: sim Date: Tue, 19 May 2026 18:23:54 +0200 Subject: [PATCH] Fido: Re-request permission when result isn't received It may happen that the OS doesn't ask for the permission: We re-request the permission every seconds until it is correctly received, with a maximum of 5 attempts, or when the user navigate back to the USB selection --- .../usb/UsbDevicePermissionManager.kt | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt index 72e9e17c99..b0e8799395 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt @@ -9,12 +9,16 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.util.Log import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager +import android.os.Build import androidx.core.app.PendingIntentCompat import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED import kotlinx.coroutines.CompletableDeferred +import java.util.Timer +import java.util.TimerTask private val Context.usbPermissionCallbackAction get() = "$packageName.USB_PERMISSION_CALLBACK" @@ -40,6 +44,10 @@ private object UsbDevicePermissionReceiver : BroadcastReceiver() { } } + fun isDeferred(device: UsbDevice): Boolean = synchronized(this) { + return pendingRequests.containsKey(device) + } + fun unregister(context: Context) = synchronized(this) { if (registered) { context.unregisterReceiver(this) @@ -75,10 +83,39 @@ class UsbDevicePermissionManager(private val context: Context) { if (context.usbManager?.hasPermission(device) == true) return true val res = CompletableDeferred() if (UsbDevicePermissionReceiver.addDeferred(device, res)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Log.d(TAG, "PermissionReceiver added for ${device.productName} (${context.packageName})") + } UsbDevicePermissionReceiver.register(context) - val intent = PendingIntentCompat.getBroadcast(context, 0, Intent(context.usbPermissionCallbackAction).apply { `package` = context.packageName }, 0, true) - context.usbManager?.requestPermission(device, intent) + schedulePermissionRequest(device, 5) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Log.d(TAG, "PermissionReceiver already added for ${device.productName}") + } } + requestPermission(device) return res.await() } + + // In case we couldn't ask for permission, retry every secondes, with a maximum of `maxRetries` + private fun schedulePermissionRequest(device: UsbDevice, maxRetries: Int) { + if (maxRetries < 1) return + Timer().schedule(object : TimerTask() { + override fun run() { + if (UsbDevicePermissionReceiver.isDeferred(device)) { + requestPermission(device) + schedulePermissionRequest(device, maxRetries - 1) + } + } + }, 1000) + } + + private fun requestPermission(device: UsbDevice) { + val intent = PendingIntentCompat.getBroadcast(context, 0, Intent(context.usbPermissionCallbackAction).apply { `package` = context.packageName }, 0, true) + context.usbManager?.requestPermission(device, intent) + } + + companion object { + private const val TAG = "UsbDevicePermissionMan" + } }