Decreased BLE startScan detected devices on Android 5.0 Lollipop

abelcookingfox picture abelcookingfox · Nov 24, 2014 · Viewed 20.1k times · Source

Short version:

In my tests with Android 5.0 Lollipop I have noticed android.bluetooth.le.BluetoothLeScanner detects BLE devices less frequently than Android 4.4 KitKat. Why is this and is there an alternative?

Long version:

I am developing an Android application, specifically for the Nexus 7 tablet, that focuses on detecting Bluetooth Low Energy (BLE) devices. The app is mainly interested in the RSSI value of the beacons, to determine their proximity to the tablet. This means I won't need to connect to the BLE device, since the RSSI value is passed to the scan callback when the device is detected.

In Android 4.4 KitKat, when I call BluetoothAdapter.startLeScan(LeScanCallback), my callback gets called only ONCE for every detected BLE device. (I have seen some discussions claim that this behaviour can differ per device) However, I am interested in the constantly changing RSSI value, so the currently recommended way is to continuously do startLeScan and stopLeScan with a set interval (250ms in my case):

public class TheOldWay {

    private static final int SCAN_INTERVAL_MS = 250;

    private Handler scanHandler = new Handler();
    private boolean isScanning = false;

    public void beginScanning() {
        scanHandler.post(scanRunnable);
    }

    private Runnable scanRunnable = new Runnable() {
        @Override
        public void run() {
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

            if (isScanning) {
                adapter.stopLeScan(leScanCallback);
            } else if (!adapter.startLeScan(leScanCallback)) {
                // an error occurred during startLeScan
            }

            isScanning = !isScanning;

            scanHandler.postDelayed(this, SCAN_INTERVAL_MS);
        }
    };

    private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            // use the RSSI value
        }
    };

}

Essentially this gives me the required results, but this process is very resource intensive and eventually leads to an unresponsive bluetooth adapter.

For these reasons I upgraded my Nexus 7 to Android 5.0 Lollipop, to see whether my BLE issues would be fixed. In Lollipop BluetoothAdapter.startLeScan(LeScanCallback) is deprecated and replaced with a new API that allows for some more control over the scanning process. From my first tests, it appears startScan does not continuously call my callback (on my Nexus 7) when the RSSI values change, so I still need to use the startScan / stopScan implementation:

@TargetApi(21)
public class TheNewWay {

    private static final int SCAN_INTERVAL_MS = 250;

    private Handler scanHandler = new Handler();
    private List<ScanFilter> scanFilters = new ArrayList<ScanFilter>();
    private ScanSettings scanSettings;
    private boolean isScanning = false;

    public void beginScanning() {
        ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
        scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
        scanSettings = scanSettingsBuilder.build();

        scanHandler.post(scanRunnable);
    }

    private Runnable scanRunnable = new Runnable() {
        @Override
        public void run() {
            BluetoothLeScanner scanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();

            if (isScanning) {
                scanner.stopScan(scanCallback);
            } else {
                scanner.startScan(scanFilters, scanSettings, scanCallback);
            }

            isScanning = !isScanning;

            scanHandler.postDelayed(this, SCAN_INTERVAL_MS);
        }
    };

    private ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);

            int rssi = result.getRssi();

            // do something with RSSI value
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);

            // a scan error occurred
        }
    };

}

As you can see, I have configured the scanner using the ScanSettings class, which allows you to set the scanMode. I use ScanSettings.SCAN_MODE_LOW_LATENCY, which has the following documentation: "Scan using highest duty cycle. It's recommended to only use this mode when the application is running in the foreground." Sounds exactly like what I want, but unfortunately I only get a beacon detect every 15 - 30 seconds, where the KitKat version shows me the same beacon every 1 - 2 seconds on this scan interval.

Do you have any idea what could be the reason for this difference? Am I missing something, maybe some new settings? Are there alternative ways of doing the above?

Thanks a lot in advance!

Abel

PS: I wanted to include more links to resources I've used, but I don't have the rep points for it yet.

Answer

davidgyoung picture davidgyoung · Nov 24, 2014

I have gotten very different results with a Nexus 5 running the new Android 5.0 scanning APIs. Detections of BLE packets came in at near real time when using SCAN_MODE_LOW_LATENCY, at every 100ms for BLE beacons transmitting at 10Hz.

You can read the full results here:

http://developer.radiusnetworks.com/2014/10/28/android-5.0-scanning.html

These tests are based off of running the open source Android Beacon Library 2.0's experimental android-l-apis branch here.

It is not obvious what the difference is in your test results, but it is possible that starting and stopping scanning is changing the results.

EDIT: it is possible the hardware is the difference. See a report of similar timings on the Nexus 4: https://github.com/AltBeacon/android-beacon-library/issues/59#issuecomment-64281446