In the context of the Harald beacon platform, we recently released a minimal open source library for monitoring iBeacons on Android. The library is named `iBeacon Scanner Android’ and can be found on our Github account.

In this post we describe our motivation behind the iBeacon Scanner Android library, how it can be used and how it performs in comparison to other beacon scanning libraries.

What are (i)Beacons?

The iBeacon protocol is a communication protocol introduced by Apple oriented towards BLE (Bluetooth Low Energy) devices. The protocol enforces devices to broadcast a particular serie of bytes at a frequent pace. Other BLE capable devices like your smartphone can pick up this signal when they are in range and respond accordingly.
In this way it can provide context to your phone at a level much more fine grained than for instance GPS. Devices capable of transmitting this iBeacon protocol are typically called iBeacons.

The iBeacon protocol

From a technical perspective, iBeacons transmit a UUID along with a numeric major and minor combination:

  • UUID: A 16 byte string identifier. Within a company, you typically use the same UUID for every beacon.
  • The major and minor: a number between 1 and 65535 used to differentiate particular iBeacon devices. You are free to use them how you want, for example you could use the major as floor level and minor for every individual object you want to highlight on that floor.

Another value that will come to mind is the RSSI. This is the Received Signal Strength Indicator, a number indicating how strong the received signal is. You can use this to determine the approximate distance from the beacon to the receiving device.

Alternative beacon protocols are: EddyStone by Google, AltBeacon by Radius Networks and some other proprietary protocols.

Let’s scan some beacons!

To get notified when you are close to a particular beacon, we should start monitoring (or scanning) for this beacon.

On iOS, this functionality is embedded in the default Location Manager framework. You simply register the UUID through a Region object and when you come into its range, you will get notified. Being deeply integrated into the operating system, scanning for iBeacons on iOS comes out of box and is heavily optimised at hardware level with low battery impact. On Android this is a whole different story.

Albeit possible since Android v4.3, it is not straightforward to scan for iBeacons; A popular beacon scanning library for Android, the Android Beacon Library overcomes a lot of these burdens and allows to deal with beacons in the same way as iOS does (and even beyond).

Therefore, since the early days of our Harald Android SDK, this Android Beacon Library has been at the foundation of our SDK. Until now. We believe the library holds some serious drawbacks and limitations which prevents us to offer the same experience and performance as we see on iOS. Below we listed the main reasons for leaving the library behind and write our own iBeacon Scanner Library for Android.

Battery drain

The main issue we experience with the Android Beacon Library is its high battery consumption. In our tests, most devices run out of battery within 24 hours!

One explanation for this high battery consumption is that the Android Beacon Library scans for all possible BLE advertisements. Especially when in the proximity of other BLE devices and beacons, this means a lot of CPU processing!

A second explanation is that the Android Beacon Library stops and starts scanning every few seconds. Each time after it stops scanning, it waits for a duration depending on how many nearby BLE devices are broadcasting. Our experience learns that instead you can better have one long running scan on a low power scan level.

Android updates

Another problem is that the Android Beacon Library is often late to update when a new Android version arrives. With a new major release of Android, often changes are pushed that impact the beacon scanning. For instance, with Android 6, location permission needs to be granted before being able to perform a scan. From Android 7, apps that stop and start more than five scans in 30 seconds get blocked from using BLE. Both changes caused a lot of confusion, and we feel that the Android Beacon Library lacks on providing the right information and updates to the library fast enough.

More services!

A final problem with Android Beacon Library is that it makes our architecture more complicated due to running in it’s own service. Our Harald SDK also runs a dedicated service in order to fetch campaign updates, validate business logic and trigger the correct beacon actions. Coordinating two services means that there is a lot of duplicated code, and one service is never certain that the other service is still running.

Write our own

With this in mind, there were enough reasons to make the switch and write our own scanning library. Hereby we wanted to:

  • Make it easy to scan for one or more iBeacons on Android.
  • Make use of ScanFilters to save power and only get notified when we enter the range of an iBeacon the app is monitoring.
  • Only start scanning when possible and pass appropriate error messages when it is not possible.
  • Correctly pass enter and exit events, even when the library restarts.
  • Make sure our library can be used in both a foreground app and in a service.
  • Scan with mode ScanSettings.SCAN_MODE_LOW_POWER to use less power.

ScanFilters

Instead of processing every BLE advertisement on the CPU (as in the case of the Android Beacon Library), we can leverage Android’s ScanFilters and move this expensive process to the hardware layer. By creating such ScanFilter, we define what pattern of bytes we expect, indicate what bytes have to be the same and what bytes can have varying values.

These ScanFilters are used as a “mask” on the hardware layer. A single XOR operation, one of the cheapest operations on computer architecture level, is enough to know if the beacon matches the pattern or not.

The following excerpt contains a utils class from our library wherein these ScanFilters are defined:

public final class ScanFilterUtils
{
    private final static int MANUFACTURER_ID = 76;

    private ScanFilterUtils()
    {
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static ScanFilter getScanFilter(@NonNull final BeacinInterface beacon)
    {
        final ScanFilter.Builder builder = new ScanFilter.Builder();

        // the manufacturer data byte is the filter!
        final byte[] manufacturerData = new byte[]
        {
                0,0,

                // uuid
                0,0,0,0,
                0,0,
                0,0,
                0,0,0,0,0,0,0,0,

                // major
                0,0,

                // minor
                0,0,

                0
        };

        // the mask tells what bytes in the filter need to match, 1 if it has to match, 0 if not
        final byte[] manufacturerDataMask = new byte[]
        {
                0,0,

                // uuid
                1,1,1,1,
                1,1,
                1,1,
                1,1,1,1,1,1,1,1,

                // major
                1,1,

                // minor
                1,1,

                0
        };

        // copy UUID (with no dashes) into data array
        System.arraycopy(ConversionUtils.UuidToByteArray(beacon.getUUID()), 0, manufacturerData, 2, 16);

        // copy major into data array
        System.arraycopy(ConversionUtils.integerToByteArray(beacon.getMajor()), 0, manufacturerData, 18, 2);

        // copy minor into data array
        System.arraycopy(ConversionUtils.integerToByteArray(beacon.getMinor()), 0, manufacturerData, 20, 2);

        builder.setManufacturerData(
                MANUFACTURER_ID,
                manufacturerData,
                manufacturerDataMask);

        return builder.build();
    }
}

Entering and leaving

The default Bluetooth scanning functionality of Android only detects when you are in range of a beacon, and does this as long as you stay within that range. It doesn’t notify however when you actually move out of this beacon range. This is unlike iOS where apps are notified of both enters and exits. This is interesting, however, for use-cases where for instance you want to know how long users dwelled inside the beacon range, or to know when a user left, and comes back in range.

In our iBeacon Scanner Android library we therefore mimic this behaviour by starting a timeout handler for every beacon enter. Every time the library is notified of being near a beacon, unless it is detected for the first time, it resets this timer. Only enter and exits events are passed to the app or service integrating our library. Enter and exit bookkeeping data is also temporary kept in memory, so that the scanning can continue over an app or service restart.

When not to scan

As defined in our wiki, there are several scenarios in which you can not scan for beacons; Disabled bluetooth is the most obvious of all. Each time before performing a scan, we validate these conditions and if not able to scan, pass the appropriate error to the app.

If the iBeacon Scanner Android is integrated inside a service, it is the responsibility of this service to write broadcast receivers that start scanning as soon as it becomes possible. You need at least: * Bluetooth state receiver * Location state receiver * Boot receiver

Doze

When testing our library, we used several Android devices and a Bluetooth iBeacon transmitter that was turned on for 15 minutes every two hours. With Android 5 devices, all enters and exits were triggered successfully; On Android 6 and 7 devices however this was not the case.

When we tested during the day, these devices were mostly working correctly, especially when moving the devices or unlocking them from time to time. At night however, we experienced the devices going into Doze mode, causing them to no longer be able to scan for beacons.

Doze is a battery saving mode, introduced in Android 6. When your device is stationary and on battery, network traffic is suspended and, jobs and alarms are deferred. Only during so called maintenance windows, apps are allowed to use the network and execute their jobs. With Android 7, an even more strict Doze phase was added, wherein also wakelocks, location services and alarms are restricted.

As a consequence, from Android 6 devices onwards our service process is getting killed when the device goes into Doze mode, only to get restarted after several minutes. Scanning will then automatically resume.

Code example

So how to get scanning? You can find the required steps on our github page: https://github.com/inthepocket/ibeacon-scanner-android.

The library can be pulled via jCenter: compile 'mobi.inthepocket.android:ibeaconscanner:1.0.0'

In your application or services onCreate you initialize IBeaconScanner:

public class MyApplication extends Application
{
    public void onCreate()
    {
        super.onCreate();

        // initialize
        IBeaconScanner.initialize(IBeaconScanner.newInitializer(this).build());
    }
}

You start scanning by implementing our result callback interface:

public interface Callback
{
    void didEnterBeacon(Beacon beacon);

    void didExitBeacon(Beacon beacon);

    void monitoringDidFail(Error error);
}

IBeaconScanner.getInstance().setCallback(this);

And then start monitoring one or more ibeacons:

public class FooActivity extends Activity
{
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_foo);

        final Beacon beacon = new Beacon.Builder()
            .setUUID("84be19d4-797d-11e5-8bcf-feff819cdc9f")
            .setMajor(1)
            .setMinor(2)
            .build();

        IBeaconScanner.getInstance().startMonitoring(beacon);
    }
}

The test result

Over the most recent weeks, we have been testing our library thoroughly. Below we are proud to share the results of our battery usage and performance tests.

As you can see in the following charts, battery usage has significantly decreased with every Android version.

android 5

android 6

android 7

In a second test case, we turned on a beacon from time to time and see if the didEnterBeacon(Beacon beacon) was called.

performance test

As you can see, on Android 6 and 7 we missed some beacon enters. This is due to the Doze as mentioned earlier.

To conclude, we are proud to say that the iBeacon Scanner Android makes iBeacon scanning on Android more reliable with significant less impact on the battery. The library is fully open source and we hope that it can further push the adoption of beacon solutions for Android!

Resources