NordicSemiconductor / Android-DFU-Library

Device Firmware Update library and Android app
http://www.nordicsemi.com/dfu
BSD 3-Clause "New" or "Revised" License
760 stars 269 forks source link

DFU fails when the Android screen is off #453

Closed roland-ryan-rm closed 6 days ago

roland-ryan-rm commented 2 weeks ago

Where do you suspect the issue?

Issue in DFU library, e.g. upload stops in the middle

Version

2.5.0 (Latest)

Describe the issue

When the Android screen is off, the DFU fails right after starting. This appears to be because the scanner cannot find the Android device. Our app has already been granted the ACCESS_BACKGROUND_LOCATION permission, so that's not relevant in this case.

After some investigation, it seems to me like the issue was introduced here. The removal of the scan filters, obstensibly to resolve issues with the DFU working at all on some devices (as seen here), stops the bluetooth scanner from being able to find the device to perform the DFU on when the screen is off.

After downloading the source code and changing BootloaderScannerLollipop to use those filters again, I can confirm that using the filters resolves this issue.

Could an option of some sort be added to allow developers to choose to support DFUs with the screen off? It would seem silly to have to fork this SDK just to support something that it already supported two years ago.

Relevant log output


Enabling indications...
Sending Enter Bootloader (Op Code = 1)
Response received (Op Code = 1, Status = 1)
Device disconnected
Restarting to bootloader mode
Scanning for new address finished with: null
philips77 commented 2 weeks ago

Hello, The change to which you pointed added an empty ScanFilter, which seemed to work back then in background. I see in Android code: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Bluetooth/android/app/src/com/android/bluetooth/le_scan/ScanManager.java;drc=d4587ecc9318a57c043a2c10dad692709a884e91;bpv=1;bpt=1;l=460 that indeed, screen on is required if all filters are empty.

I think we could fix the issue and simplify it by adding non-empty filters form some Android version. Could you try the DFU background scan on older Android device and try to find at what point has it stopped working?

philips77 commented 1 week ago

Here's the commit that "fixed" that: https://cs.android.com/android/_/android/platform/packages/modules/Bluetooth/+/7f707ac3123f62e78cc090b934d3146326fc52bc:android/app/src/com/android/bluetooth/gatt/ScanManager.java;dlc=4f3c7a98febe594469dd1bd82e0086c24bbc3b51 which would indicate API 33 or 34 (June 2023).

roland-ryan-rm commented 1 week ago

I think we could fix the issue and simplify it by adding non-empty filters form some Android version. Could you try the DFU background scan on older Android device and try to find at what point has it stopped working?

Here's the commit that "fixed" that: https://cs.android.com/android/_/android/platform/packages/modules/Bluetooth/+/7f707ac3123f62e78cc090b934d3146326fc52bc:android/app/src/com/android/bluetooth/gatt/ScanManager.java;dlc=4f3c7a98febe594469dd1bd82e0086c24bbc3b51 which would indicate API 33 or 34 (June 2023).

I just tried it with a device running API 31 and still had the issue, but a different device running API 29 did not have the issue. So I'd guess the breaking change was made in API 30 or 31.

roland-ryan-rm commented 6 days ago

Awesome, thanks for the quick turnaround on this! @philips77 any idea when the next release will be?

philips77 commented 6 days ago

I have one more thing to fix in the app. I'll release tomorrow.

roland-ryan-rm commented 2 days ago

Can confirm the fix works after properly overriding getDeviceSelector() in my subclass of DfuBaseService. Code is below, for anyone else that may run into this issue. Thanks for the quick turnaround on this!

private var deviceAddress: String? = null

override fun onHandleIntent(intent: Intent?) {
    super.onHandleIntent(intent)
    deviceAddress = intent?.getStringExtra(EXTRA_DEVICE_ADDRESS)
}

override fun getDeviceSelector(): DfuDeviceSelector {
    return CustomDeviceSelector()
}

inner class CustomDeviceSelector : DfuDeviceSelector {
    override fun matches(
        device: BluetoothDevice,
        rssi: Int,
        scanRecord: ByteArray,
        originalAddress: String,
        incrementedAddress: String
    ): Boolean {
        return originalAddress == device.address || incrementedAddress == device.address
    }

    override fun getScanFilters(dfuServiceUuid: ParcelUuid): MutableList<ScanFilter> {
        return mutableListOf<ScanFilter>().apply {
            add(ScanFilter.Builder().setServiceUuid(dfuServiceUuid).build())
            deviceAddress?.let {
                add(ScanFilter.Builder().setDeviceAddress(it).build())
                add(ScanFilter.Builder().setDeviceAddress(BootloaderScannerFactory.getIncrementedAddress(it)).build())
            }
        }
    }
}