Bluetooth LE Scan fails in the background - permissions

davidgyoung picture davidgyoung · Sep 22, 2015 · Viewed 39.9k times · Source

The following code works great on my Nexus 9 running Android 5.1.1 (Build LMY48M), but won't work on a Nexus 9 running Android 6.0 (Build MPA44l)

List<ScanFilter> filters = new ArrayList<ScanFilter>();
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setManufacturerData((int) 0x0118, new byte[]{(byte) 0xbe, (byte) 0xac}, new byte[]{(byte) 0xff, (byte)0xff});
ScanFilter scanFilter = builder.build();
filters.add(scanFilter);
mBluetoothLeScanner.startScan(filters, settings, new ScanCallback() {
  ...
});

On Android 5.x, the above code yields a callback when a manufacturer advertisement matching the scan filter is seen. (See example Logcat output below.) On the Nexus 9 with MPA44l, no callbacks are received. If you comment out the scan filter, callbacks are received successfully on the Nexus 9.

09-22 00:07:28.050    1748-1796/org.altbeacon.beaconreference D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=00:07:80:03:89:8C, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={280=[-66, -84, 47, 35, 68, 84, -49, 109, 74, 15, -83, -14, -12, -111, 27, -87, -1, -90, 0, 1, 0, 1, -66, 0]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-64, mTimestampNanos=61272522487278}

Has anybody seen ScanFilters work on Android M?

Answer

dinosaur picture dinosaur · May 4, 2016

I had a similar problem with an app connecting to bluetooth. Not LE ScanFilter, but it was a permissions issue just like the OP had.

Root cause is that starting with SDK 23, you need to prompt the user for permissions at runtime using Activity's requestPermissions() method.

Here's what worked for me:

  1. Add one of the following two lines to AndroidManifest.xml, inside the root node:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    
  2. In your Activity, before attempting to connect to bluetooth, call Activity's requestPermissions() method, which opens a system dialog to prompt the user for the permission. The permissions dialog opens in a different thread, so be sure to wait for the result before trying to connect to bluetooth.

  3. Override Activity's onRequestPermissionsResult() to handle the result. This method will really only need to do something if the user refused to grant the permission, to tell the user that the app can't do the bluetooth activity.

This blog post has some example code that uses AlertDialogs to tell the user what's going on. It is a good starting point but has some shortcomings:

  • It doesn't handle waiting for the requestPermissions() thread to finish
  • The AlertDialog wrapping the call to requestPermissions() seems extraneous to me. A bare call to requestPermissions() is sufficient.