androidbluetooth-lowenergynordic-semi

BLE Scanner in Foreground service doesn't return scanResults if initialized from BOOT_COMPLETED


So the situation is as follows. I have an Android app running on a AndroidTV device with Android 10.

The app contains a foreground service, which continually scans for specific devices (by MAC address). Like so:

import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat
import no.nordicsemi.android.support.v18.scanner.ScanCallback
import no.nordicsemi.android.support.v18.scanner.ScanFilter
import no.nordicsemi.android.support.v18.scanner.ScanResult
import no.nordicsemi.android.support.v18.scanner.ScanSettings

fun scan() {
    BluetoothLeScannerCompat.getScanner()
        .startScan(
            /* MAC address filters */ filters, 
            ScanSettings.Builder()
                .setLegacy(false)
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .setUseHardwareBatchingIfSupported(false)
                .setReportDelay(5000)
                .build(), 
            /* Standard callback */ callback
        )
}

This works fine when the app is started from the launcher (MainActivity):

override fun onCreate(savedInstanceState: Bundle?) {
    // ... UI stuff ...

    startForegroundService(Intent(this, BleService::class.java))
}

However, when the TV box is restarted, the service gets initialized like this (BOOT_COMPLETED) receiver:

class BootCompletedReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
            context?.startForegroundService(Intent(context, BleService::class.java))
        }
    }
}

Now the weird thing is that when the service is started like this, there aren't any scanResults! Even when the Bluetooth device is in range. However, consequently opening the app from the launcher and restarting the scanner does yield a result.

BluetoothAdapter        D  isLeEnabled(): ON
BluetoothLeScanner      D  onScannerRegistered() - status=0 scannerId=5 mScannerId=0
BluetoothScanJob        I  Start Scan
BluetoothS...b$callback I  [onBatchScanResults]: Results=[]

A few things to consider:

<manifest <!-- Removed for brevity -->>

    <!-- Removed for brevity -->

    <!-- Bluetooth access up to SDK 30 -->
    <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />

    <!-- Bluetooth access from SDK 30 onwards -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="31" />

    <!-- Foreground service -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>

    <!-- Autostart service -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <!-- Require BLE hardware capability -->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true"/>

    <application <!-- Removed for brevity -->>

        <receiver
            android:name=".service.BootCompletedReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service
            android:name=".service.BleService"
            android:label="@string/service_name"
            android:description="@string/service_description"
            android:exported="false"
            android:foregroundServiceType="connectedDevice"/>

        <activity
            android:name=".ui.main.MainActivity"
            android:exported="true">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            tools:node="remove">
        </provider>

    </application>

</manifest>


Solution

  • Turns out that when the service is started from the background, Android requires (even if it's a foreground service) the ACCESS_BACKGROUND_LOCATION to be granted on Android <= 11.

    Please note that

    If you request a foreground location permission and the background location permission at the same time, the system ignores the request and doesn't grant your app either permission.

    https://developer.android.com/develop/sensors-and-location/location/permissions#request-background-location