@@ -78,186 +78,63 @@ class USBPermissionManager(private val activityContext: Context)
7878
7979 /* *
8080 * Requests permission for the specified USB device.
81- * Returns a flow the emits the permission result.
81+ * Returns a flow that emits the permission result once and closes .
8282 *
8383 * @param device The USB device to request permission for.
84- * @return Flow<PermissionResult> The permission request outcome .
84+ * @return Flow<PermissionResult> emitting a single result .
8585 */
8686 fun requestPermissionFor (device : UsbDevice ): Flow <PermissionResult > = callbackFlow {
8787 val deviceKey = getDeviceKey(device)
8888
89- // Check if permission already exists .
89+ // Short-circuit if permission is already granted .
9090 if (hasPermission(device))
9191 {
9292 trySend(PermissionResult .Granted )
9393 close()
9494 return @callbackFlow
9595 }
9696
97- // Create a broadcast receiver to handle the permission response.
9897 val permissionReceiver = object : BroadcastReceiver ()
9998 {
10099 override fun onReceive (context : Context , intent : Intent )
101100 {
102- Timber .d(" === BROADCAST RECEIVED ===" )
103- Timber .d(" Action: ${intent.action} " )
104- Timber .d(" Expected: $ACTION_USB_PERMISSION " )
105- Timber .d(" Context: ${context.javaClass.simpleName} " )
106- Timber .d(" Thread: ${Thread .currentThread().name} " )
101+ if (intent.action != ACTION_USB_PERMISSION ) return
107102
108- if (ACTION_USB_PERMISSION == intent.action)
109- {
110- Timber .d(" USB permission broadcast received!" )
111-
112- // Check what Android actually sends
113- val androidDevice = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU )
114- {
115- intent.getParcelableExtra(UsbManager .EXTRA_DEVICE , UsbDevice ::class .java)
116- }
117- else
118- {
119- @Suppress(" DEPRECATION" )
120- intent.getParcelableExtra(UsbManager .EXTRA_DEVICE )
121- }
122-
123- Timber .d(" Android provided device: ${androidDevice?.deviceName} " )
124- Timber .d(" Android device key: ${androidDevice?.let { getDeviceKey(it) }} " )
125- Timber .d(" Expected device key: $deviceKey " )
126- Timber .d(" Permission granted flag: ${intent.getBooleanExtra(UsbManager .EXTRA_PERMISSION_GRANTED , false )} " )
127-
128- // Debug: Log all extras in the intent
129- val extras = intent.extras
130- if (extras != null )
131- {
132- for (key in extras.keySet())
133- {
134- Timber .d(" Intent extra: $key = ${extras.get(key)} " )
135- }
136- }
137- else
138- {
139- Timber .d(" No extras in intent" )
140- }
141-
142- val receivedDevice = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU )
143- {
144- intent.getParcelableExtra(UsbManager .EXTRA_DEVICE , UsbDevice ::class .java)
145- }
146- else
147- {
148- @Suppress(" DEPRECATION" )
149- intent.getParcelableExtra(UsbManager .EXTRA_DEVICE )
150- }
151-
152- // Verify the response is for our device.
153- if (receivedDevice != null && getDeviceKey(receivedDevice) == deviceKey)
154- {
155- val broadcastGrantedFlag = intent.getBooleanExtra(UsbManager .EXTRA_PERMISSION_GRANTED , false )
156- Timber .d(" Broadcast granted flag: $broadcastGrantedFlag " )
157-
158- // CRITICAL: Don't trust the broadcast flag - check actual permission state
159- val actuallyHasPermission = usbManager.hasPermission(receivedDevice)
160- Timber .d(" Actual permission state from UsbManager: $actuallyHasPermission " )
161-
162- permissionCache[deviceKey] = actuallyHasPermission
163-
164- val result = if (actuallyHasPermission)
165- {
166- Timber .d(" 🟢 Permission GRANTED - confirmed by UsbManager" )
167- PermissionResult .Granted
168- }
169- else
170- {
171- Timber .d(" 🔴 Permission DENIED - confirmed by UsbManager" )
172- PermissionResult .Denied
173- }
174-
175- trySend(result)
176- close()
177- }
178- else
179- {
180- if (receivedDevice != null ) {
181- Timber .d(
182- " Received permission for an unexpected device: ${
183- getDeviceKey(
184- receivedDevice
185- )
186- } "
187- )
188- }
189- else
190- {
191- Timber .d(" Received permission but the device is null." )
192- }
193-
194- trySend(PermissionResult .Error (" Device mismatch in permission response" ))
195- close()
196- }
197- }
198- else
199- {
200- Timber .d(" Ignoring broadcast with different action" )
201- }
202- }
203- }
204-
205- // Register receiver and request permission
206- try
207- {
208- kotlinx.coroutines.MainScope ().launch {
209- val intentFilter = IntentFilter (ACTION_USB_PERMISSION )
210-
211- Timber .d(" === REGISTERING RECEIVER ===" )
212- Timber .d(" Context: ${activityContext.javaClass.simpleName} " )
213- Timber .d(" Filter: ${intentFilter.actionsIterator().asSequence().toList()} " )
103+ val receivedDevice =
104+ intent.getParcelableExtra(UsbManager .EXTRA_DEVICE , UsbDevice ::class .java)
214105
215- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU )
106+ // Ignore broadcasts for other devices.
107+ if (receivedDevice == null || getDeviceKey(receivedDevice) != deviceKey)
216108 {
217- activityContext.registerReceiver(
218- permissionReceiver,
219- intentFilter,
220- Context .RECEIVER_NOT_EXPORTED
221- )
222- }
223- else
224- {
225- activityContext.registerReceiver(permissionReceiver, intentFilter)
109+ Timber .w(" Permission broadcast for unexpected device: ${receivedDevice?.deviceName} " )
110+ return
226111 }
227112
228- Timber .d(" Receiver registered successfully" )
229- Timber .d(" IntentFilter actions: ${intentFilter.actionsIterator().asSequence().toList()} " )
230- Timber .d(" Current thread: ${Thread .currentThread().name} " )
231- Timber .d(" Context: ${activityContext.javaClass.simpleName} " )
232-
233- // Create intent with the device as an extra so it comes back in the broadcast
234- val permissionIntent = Intent (ACTION_USB_PERMISSION ).apply {
235- putExtra(UsbManager .EXTRA_DEVICE , device)
236- }
113+ // EXTRA_PERMISSION_GRANTED is unreliable on some Android versions; query directly.
114+ val granted = usbManager.hasPermission(receivedDevice)
115+ Timber .d(" Permission result for ${device.deviceName} : granted=$granted " )
116+ permissionCache[deviceKey] = granted
237117
238- val pendingIntent = PendingIntent .getBroadcast(
239- activityContext,
240- device.deviceId,
241- permissionIntent,
242- PendingIntent .FLAG_UPDATE_CURRENT or PendingIntent .FLAG_IMMUTABLE
243- )
118+ trySend(if (granted) PermissionResult .Granted else PermissionResult .Denied )
119+ close()
120+ }
121+ }
244122
245- usbManager.requestPermission(device, pendingIntent)
123+ // Register before calling requestPermission() so we don't miss it
124+ val intentFilter = IntentFilter (ACTION_USB_PERMISSION )
125+ activityContext.registerReceiver(permissionReceiver, intentFilter, Context .RECEIVER_NOT_EXPORTED )
126+ Timber .d(" Permission receiver registered for ${device.deviceName} " )
246127
247- Timber .d(" Permission request sent for device: ${device.deviceName} " )
248- Timber .d(" Using action: $ACTION_USB_PERMISSION " )
249- Timber .d(" Device key: $deviceKey " )
250- }
128+ val pendingIntent = PendingIntent .getBroadcast(
129+ activityContext,
130+ device.deviceId,
131+ Intent (ACTION_USB_PERMISSION ),
132+ PendingIntent .FLAG_UPDATE_CURRENT or PendingIntent .FLAG_IMMUTABLE
133+ )
251134
252- }
253- catch (error: Exception )
254- {
255- Timber .e(error, " Failed to register receiver or request permission" )
256- trySend(PermissionResult .Error (" Failed to request permission: ${error.message} " ))
257- close()
258- }
135+ usbManager.requestPermission(device, pendingIntent)
136+ Timber .d(" Permission request sent for ${device.deviceName} " )
259137
260- // Cleanup when flow is cancelled.
261138 awaitClose {
262139 try
263140 {
0 commit comments