David G. Young 29 Sep 2015 Permalink
The new Android 6.0 release has several important changes that affect apps detecting bluetooth beacons. If you have a beacon-based app already in the Play Store, or are planning a new beacon-based app, you’ll need to make updates to keep your app from breaking as users transition to Android 6.0.
While this article focusses largely on the impact for users of the Android Beacon Library and Radius Networks’ ProximityKit and CampaignKit libraries that are built upon it, the same issues described here apply to any app that detects beacons. If you are using a different vendor’s SDK, it is important to make sure they will continue to work on Android 6.0.
The biggest change for beacon apps in Android 6.0, codenamed Marshmallow, and sometimes called just “M”, has to do with permissions. Just like iOS, Android now implements permissions at runtime instead of the traditional way of granting permissions at install time. Apps designed for Marshmallow (SDK 23 and above) must add code to prompt users for some permissions after the app starts up, otherwise they will not be granted.
Not all permissions, however, work this way. Permissions marked as PERMISSION_NORMAL
are still granted the old fashioned way: at install time. For beacon apps, two important permissions continue to follow the old model: android.permission.BLUETOOTH
and android.permission.BLUETOOTH_ADMIN
, both of which is needed to scan for beacons. Because these permissions still use the old behavior, nothing really changes with them in Marshmallow.
While the bluetooth permissions are enough to allow your legacy app to scan for beacons on Android 6.0, these permissions only allow it to do so in the foreground. Background scanning for bluetooth devices in Android 6.0, including beacons, now requires either android.permission.ACCESS_FINE_LOCATION
or android.permission.ACCESS_COARSE_LOCATION.
This means that apps to be installed on Android 6.0 devices that want to discover beacons in the background must make changes. Apps must declare one of these location permissions in AndroidManifest.xml. This is true even for legacy apps that don’t target SDK 23 used for Android Marshmallow.
What’s more, these permissions follow the new runtime model. This means that apps that target SDK 23 must also prompt the user for a location permission after the app is launched. If you fail to prompt for and get this permission, you’ll get the following error in LogCat when you try to do a bluetooth scan in the background, and no beacons will be detected:
09-22 22:35:20.152 5158 5254 E BluetoothUtils: Permission denial: Need ACCESS_COARSE_LOCATION or
ACCESS_FINE_LOCATION permission to get scan results
To avoid your Android Marshmallow users getting this error, you must at least update your legacy AndroidManifest.xml with the following:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION”/>
And you must also add code like the following to an Activity if your app targets Marshmallow (if you set targetSdkVersion 23
):
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android M Permission check
if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect beacons.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
}
});
builder.show();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[],
int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_COARSE_LOCATION: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "coarse location permission granted");
} else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
return;
}
}
}
The code above will first check to see if the location permission has already been granted. If not, it will prompt the user with an AlertDialog
, “Please grant location access so this app can detect beacons.” This dialog is not strictly required, but it is recommended to first explain to the user why your app needs the permission. While this message is just an example, you should probably fine tune the verbiage to explain in less technical terms what functional benefit users gets from granting this permission. Otherwise, users will likely deny the permission to keep the app from tracking them.
After presenting this dialog, the system then calls the new Android requestPermissions
method that actually does the prompting. The dialog screenshot above shows what the user actually sees. Android also provides a callback to the app telling you the result of what the user decided (see the onRequestPermissionResult
callback), but it is important to note that users can change their mind later on via settings and turn permissions on or off. In that case, the callback method won’t get notified.
It is important to note that the location permission does not replace the need to request BLUETOOTH
and BLUETOOTH_ADMIN
permissions – all three are now necessary. And just because you get user to grant the permission doesn’t automatically turn bluetooth on. It still needs to be turned on separately, so you will need to keep prompting your users to do that, too.
Two other supplemental permissions used by the Android Beacon Library, android.permission.RECEIVE_BOOT_COMPLETED
(used to start looking for beacons after phone startup) and android.permission.INTERNET
(used to download an updated database of device-specific distance estimation formulas) also follow the old model. For beacon apps using the Android Beacon Library, these two supplemental permissions will continue working the same way with Android Marshmallow.
The other big change for beacon apps with Android Marshmallow is that it implements two power saving modes which affect background operations. Fortunately, apps using the Android Beacon Library, and other Radius Networks SDKs built upon it (like ProximityKit and CampaignKit) don’t need to make any changes to their apps as the library handles all the details of keeping beacon scanning going in the background. If you use other code to scan for beacons, or if you are interested in how the Android Beacon Library handles the changes, read on. If you’d rather just take our word for it, skip to the MAC address section below.
Android’s new Doze feature will put your device in a battery saving mode when the screen is off and it is not plugged in, and the operating system detects that it is sitting motionless based on accelerometer input. This is intended to cover cases where it is sitting on a desk, but not when it is in your pocket or purse while you are moving around, or in a moving vehicle.
To understand how this affects background beacon scanning with the Android Beacon Library, it is important to understand how the library handles periodic background scanning. It uses a background Service
that uses the Android Handler
mechanism to periodically start and stop scanning. It also uses the AlarmManager
to restart the scanning service if the app and service are terminated due to low-memory conditions or due to the user closing the app from the task switcher.
Doze does not affect the ability to do bluetooth scans or run Handler
-based timers, but it does affect the ability to use the Android AlarmManager
system, which some beacon-based systems use to schedule bluetooth scans. For apps based on the Android Beacon Library, the disabling of Alarms does not cause problems in scanning for beacons because it only uses Alarms as a backup should Handler-based timers fail. Tests on a Nexus 9 running the third preview release of Android M (build number MPA44l) show that beacon detection continues normally under Doze.
App Standby is similar to Doze, but it is triggered under different conditions. If a user doesn’t explicitly launch an app for several hours or several days (Google won’t say specifically how long it takes), the app is put into App Standby mode whenever the device is not connected to a charger. (App Standby also may be avoided if the app sends visible notifications.) Once an App is in App Standby, it generally cannot access the network, something that might prevent calling web services based on beacon detections. That said, App Standby does not specifically affect beacon detection with the Android Beacon Library, because bluetooth scanning service continues to run normally, and the AlarmManager
and Handler
-based timers continue to work. Again, this has been verified by tests on a Nexus 9 running the third preview release of Android M.
Being blocked from making network calls can still affect your app. If your app only needs to periodically contact a web service, App Standby can keep it from trying to do so. In theory, an app in App Standby can still make network calls once in awhile. As Google says, “if the device is idle for long periods of time, the system allows idle apps network access around once a day.” Unfortunately, there is no documentation on how this works. If your app uses one or more libraries that require background network access and the app requires background network access in its own code, there is no guarantee which codebase will try background network access first, and which one will actually succeed “around once per day.”
Fortunately, there are two other ways an app in AppStandby can get network access aside from the squishy “around once per day” promise.
The app receives a new “high priority” message from Google Cloud Messaging (GCM). Receiving such a push notification amounts to a get out of jail free card. But Google has yet to release details on how to send this new high priority push notification. What’s more, it’s based on Google Play Services, which are not available on many devices like Amazon’s Kindle Fire line, the vast majority of tablets and handsets in China, and a growing number of non-Google Android devices in India and other parts of the world.
The phone is charging. Any app that has been put into App Standby temporarily regains network access after charging is connected. An app can therefore register to receive a broadcast message when power is connected and then attempt to make network calls at that time. (It’s probably a good idea to delay for a minute or so to wait for full network access to be restored.) The code below shows how you set this up in the AndroidManifest.xml.
<receiver android:name="com.mycompany.myapp.MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
</intent-filter>
</receiver>
While the Android Beacon Library does not require background network access, Radius Networks’ ProximityKit and CampaignKit libraries for Android do make periodic network calls to sync new data with the server. Using techniques like described above, new releases of these libraries for Android 6.0 will continue to sync data even in the background.
The Android Beacon Library’s scanning service starts up in the background by using a broadcast receiver that listens for the RECEIVE_BOOT_COMPLETED event. This same broadcast receiver also handles alarms that go off after five minutes of the scanning service not being active. These alarms ensure that the service is able to keep looking for beacons even if the operating system had to temporarily evict the app due to a low memory condition. Based on testing with the Nexus 9 and the Android Marshmallow preview release, these capabilities are all unchanged with the new OS update.
As described above, the only limitation with Doze is using Alarms to restart beacon scanning. Because Alarms are disabled in Doze, the Android Beacon Library cannot use Alarms to restart itself if it is manually killed by a user or if memory pressure causes the operating system to terminate the app. However, because interacting with the device will cause it to exit Doze, there is no easy way for a user to kill an app while it is in Doze. Similarly, because a user is not interacting with a device while it is in Doze, it is very unlikely that an app will start using a large amount of memory causing the a beacon-based app to be shut down to free up memory. In the very unlikely event that this does happen, the app would stop scanning for beacons until the next time power is connected or the phone rebooted.
The table below shows the effect of different Marshmallow modes on the scanning and auto-restart techniques used by the Android Beacon Library.
Platform/State | Timed BLE scans | Low Power Scan Filter | Restart Scan on Alarm | Restart Scan on Power Connect |
---|---|---|---|---|
Lollipop | Yes | Yes | Yes | Yes |
Marshmallow | Yes | Yes | Yes | Yes |
App Standby | Yes | Yes | Yes | Yes |
Doze | Yes | Yes | NO | N/A* |
* Power connection automatically disables Doze
Apps on Android 6.0 can no longer read the device’s own bluetooth MAC address, and this bluetooth MAC address is spoofed when sending out packets while doing an active bluetooth scan. The MAC address is randomized each time you start transmitting as a beacon (or doing bluetooth advertising for other purposes.) While transmitting as a beacon using the Android Beacon Library’s BeaconTransmitter
class continues to work normally, receiving devices will see that it sends a different spoofed MAC address each time transmission starts.
Interestingly, blocking access to reading bluetooth MAC addresses is part of the rationale behind requiring location permission to scan for beacons. This restriction is already discussed in the permissions section above. But it is worth noting here Google’s justification for this permission change being based on reading MAC addresses:
“The MAC address and the SSID can be used to identify surrounding devices. Knowing the location of these devices can be used to infer the phone location. As we have no way to know if the app would try to infer user’s location via WiFi scans we take a conservative approach to protect the user’s privacy. If an app is targeting pre M SDK does not have ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION it will get scan results only if it is in the foreground. For M SDK apps the location permission is required to get scan results” – Google
While there are big changes in Android Marshmallow that affect beacon apps, users of Radius Networks SDKs like the Android Beacon Library, ProximityKit and CampaignKit are well positioned to make a smooth transition. The libraries already handle most of the details. If you have an existing beacon app that does not yet target SDK 23 and only needs to work in the foreground, no changes are needed. If you have a beacon app that needs to work in the background, you need to update it to request proper location permissions as described in the first section. Users of ProximityKit and CampaignKit will need to upgrade their SDKs to keep background syncs happening on Android 6.0.