androidkotlinbluetoothbluetooth-lowenergyandroid-bluetooth

In Android, how to specify on which thread the BLE ScanCallback is running?


Android. Kotlin.

Hello, I would appreciate some help understanding how threads work here :)

I have a BLE scanner class. Its method scan() runs everything with Dispatcher.IO. I managed to confirm that its code is running NOT on the main thread. Within it I create a suspendCancellableCoroutine and within it I create a ScanCallback. I would assume the callback's method would run on the same thread that the callback was created. But it doesn't - it runs on the main thread and could potentially block the UI.

QUESTION: is there a way to make sure the ScanCallback's methods like onScanResult and onScanFailed run on the same thread that the rest of FakeScaner.scan() method is running on?

Reason for that: I needed to stop the scanner in some situations, sleep for some seconds and restart it. I managed to do a workaround, but I would still like to figure out why this works as it works and how to change it if possible :)

class FakeScanner(
    private val context: Context,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {

    private val bluetoothAdapter: BluetoothAdapter by lazy {
        val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        requireNotNull(bluetoothManager.adapter)
    }

    suspend fun scan(timeout: Duration?): Map<MyDevice, SignalStrength> =
        withContext(ioDispatcher) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                Log.d(TAG, "This is running MAIN thread")
            } else {
                Log.d(TAG, "This is running NOT on MAIN thread")
            }

            /**
             * Some code here
             */

            suspendCancellableCoroutine { cont ->

                val bleDevices = mutableMapOf<BluetoothDevice, SignalStrength>()

                val scanCallback = object : ScanCallback() {

                    @Synchronized
                    override fun onScanResult(callbackType: Int, result: ScanResult?) {

                        if (Looper.myLooper() == Looper.getMainLooper()) {
                            Log.d(TAG, "This is running MAIN thread")
                        } else {
                            Log.d(TAG, "This is running NOT on MAIN thread")
                        }

                        try {
                            result?.device?.let {
                                bleDevices.put(it, SignalStrength(result.rssi))
                            }
                        } catch (t: Throwable) {
                            if (cont.isActive) { cont.resumeWithException(t) }
                        }
                    }

                    @Synchronized
                    override fun onScanFailed(errorCode: Int) {
                        try {
                            // Some logic about scan attempt limits..
                            when (errorCode) {
                                SCAN_FAILED_ALREADY_STARTED -> { /* Restart scanner after delay */ }
                                else -> { /* Logic about other error codes */ }
                            }
                        } catch (t: Throwable) {
                            if (cont.isActive) { cont.resumeWithException(t) }
                        }
                    }
                }

                try {
                    // Start scanning
                } catch (t: Throwable) {
                    if (cont.isActive) {
                        cont.resumeWithException(t)
                    }
                }

                cont.invokeOnCancellation {
                    try {
                        // Stop scanner here
                    } catch (t: SecurityException) {
                        // Nothing to do here
                    }
                }
            }
        }
}

Solution

  • Nope, unfortunately not. It is hardcoded here that it will run on the main thread: https://android.googlesource.com/platform/packages/modules/Bluetooth/+/aa58f747f37494c619a9afae1622f56001bc07fb/framework/java/android/bluetooth/le/BluetoothLeScanner.java#543.

    Previously in Android 4.4, the scan results were delivered directly on the Binder thread that internally receives the results from the Bluetooth process. I don't really know why they changed it this way.

    If you want to handle the results on a different thread, you need to post the results yourself manually to that thread.