Decreased BLE startScan detected devices on Android 5.0 Lollipop

20.4k views Asked by At

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.

5

There are 5 answers

6
davidgyoung On BEST ANSWER

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

1
marp On

I have experienced very similar results with my Nexus 4, in both KitKat and Lollipop.

With KitKat the bluetooth adapter also eventually went unresponsive; at first I though that it could be related to a short scan interval (200ms) but increasing that number to even a second didn't help, in that matter I found that, when unresponsive disabling and enabling the adapter programmatically, sometimes solves the problem. Unfortunately I can't say that it works all the time.

Now with Lollipop, in which I had high hopes to solve this issues, I experienced the same behaviour that you describe. I also had to use the startScan / stopScan implementation, getting similar results regarding the detection times. Sadly, I haven't found a work around to get results more quickly.

Based on what you describe I suppose it could be a hardware issue, even though the Nexus 7 and Nexus 4 are from different manufacturers (Asus and LG).

I know I'm not providing much help here besides trying to answer your question about you missing something; I don't think so, I think the problem is something like the hardware or the bluetooth API that still doesn't behave the way it should across different devices.

0
Nowa Concordia On

Beyond API 21 android uses SCAN_MODE_LOW_POWER by default. SCAN_MODE_LOW_POWER

Try SCAN_MODE_BALANCED and see if it gets better.
SCAN_MODE_BALANCED

2
Joel Kindem On

if you search for BW13_DayOne_Session1 Bluetooth Advanced on google, you will find a pdf document that gives you the latencies for devices based on the settings for discovery (see page 8). I'm guessing your problem has to do with these timings. You can verify by figuring out the advertising configuration for the device you are testing (Adv Int, Duty Cycle) then figure out what the API settings are doing for configuring the scan interval, etc. Once you have these, you can then use that table to interpolate to see if your getting the results you expect.

I know this is a software site, but often when interfacing with hardware you need to know the protocol otherwise your shooting in the dark.

1
kurt On

I don't have 50 reputation for a comment yet, so bear with me, this comment will be in the form of an answer. In your code, shouldn't this part:

if (isScanning) { scanner.startScan(...)

be this instead:

if (!isScanning) {
 scanner.startScan(...)

Because following your code, you're calling stopScan() before starting a scan. It may not have a direct effect on the result if the stopScan() method is idempotent/safe. But you know, for the sake of code intelligibility you should edit the question. And do the same to your code, sometimes byzantine things are at play ;)

Have you tried larger values for SCAN_INTERVAL_MS? If yes, how large?