Google Proximity Beacons – Part 2

Dave Smith
Dave Smith
Google Proximity Beacons – Part 2

Note: The following discussion relates to library code available in Google Play Services v7.8. If you are using later versions of Play Services, the API surface area may have changed since this article was written.

We saw in the previous post how Google's Proximity Beacon API could be used to create and attach additional contextual data to the otherwise simple advertisement of a Bluetooth beacon. In this post, we are going to dive deeper into the client side. How do we get our user's devices to recognize these beacons and retrieve the information created by the beacon management admin?

Note: You can get the full sample code for the NearbyBeacons example discussed here on GitHub.

Remember Attachments?

As a quick review, we can supply one or more pieces of data that a client should receive when a particular beacon is observed using attachments. An attachment is a raw data payload (up to 1KB) that provides more contextual information about a beacon's role than the 31 byte advertisement it broadcasts through the air.

I could create attachments containing JSON data, for example, representing offers in a retail store:

{
    "section": "Athletic Wear",
    "latest_offer": "Running shorts, buy one get one free!"
},
{
    "section": "Sporting Goods",
    "latest_offer": "Half off all sportsballs!"
},
{
    "section": "Electronics",
    "latest_offer": "Free Google TV with any purchase!"
}

Each attachment is created with a namespaced type that clients can use to group and filter the attachments they see.

{
  "namespacedType": "beacon-deployment-demo/offer",
  "data": "<BASE64 ENCODED RAW DATA>"
}

Attachments, as we will see shortly, will become the backbone of the data our client application is allowed to access for any given beacon. They have to be created ahead of time and associated to a beacon's advertised identifier using the Proximity Beacon API.

Observing...The Hard Way

The Proximity Beacon API supplies the beaconinfo.getforobserved method to retrieve attachment data associated with a particular beacon advertisement from the mobile client. Here, the client code is responsible for converting the advertisements discovered during a Bluetooth Low Energy (LE) scan into AdvertisedId instances one can post to the API. One advantage over the previous API calls is this one allows use of an API Key credential—meaning you won't have to go through the OAuth process as we did before on the observer side.

This is what an example getforobserved request would look like over the wire:

POST /v1beta1/beaconinfo:getforobserved?key=[API_KEY]
{
  "observations": [
    {
      "advertisedId": {
        "type": "EDDYSTONE",
        "id": "RCVg2jS9IEqe1wAAAAAwOQ=="
      },
      "timestampMs": "2015-09-10T21:00:44.000Z"
    }
  ],
  "namespacedTypes": "beacon-deployment-demo/offer"
}

In addition to the list of beacons we've observed (and when we saw them), this request also requires one or more namespacedTypes, which will filter the returned attachments. The response body would look something like the following:

{
  "beacons": [
    {
      "advertisedId": {
        "type": "EDDYSTONE",
        "id": "RCVg2jS9IEqe1wAAAAAwOQ=="
      },
      "beaconName": "beacons/3!442560da34bd204a9ed7000000003039",
      "attachments": [
        {
          "namespacedType": "beacon-deployment-demo/offer",
          "data": "…"
        },
        {
          "namespacedType": "beacon-deployment-demo/offer",
          "data": "…"
        }
      ]
    }
  ]
}

This example beacon has two attachments matching the requested type —the client is now free to decode and parse the data associated with each. This is not code I'd prefer to write as a client-side mobile developer, so if there is a simpler way to retrieve this information I'm all for it!

An Easier Way (Active Scanning)

Thankfully, Google has provided us with a simpler solution in the form of the Nearby Messages API —part of Google Play Services. With Nearby Messages, we can create a simple subscription to published messages coming from devices in close proximity. Google has added support for beacons, such that any beacon advertisement discovered will be processed queried against the Proximity Beacon API. Matching attachments will be delivered to the subscriber as messages.

The beauty of this solution is that the scanning, API interactions, and parsing of the data are all handled by Play Services. Your client code simply has to declare what message types it expects (defined by the namespacedType used to create them with the API) and react to the attachments delivered via the discovery callbacks. It is also beneficial because our application does not need to ask for any special permissions —Play Services is handling that part.

We'll take a more concrete look at how this works, but first a bit of setup...

More APIs and Credentials

In order to allow Nearby Messages to access our beacon data, we need to enable the Nearby Messages API on our Google Developers Console project. This must be the exact same project where the Proximity Beacon API is enabled:

  1. In the console, select APIs & auth > APIs
  2. Search for Nearby Messages API and select it
  3. Click the Enable API button at the top

If you go back, Nearby Messages API should now show up under the Enabled APIs tab.

You may have guessed from the previous discussion, but there's another credential we need to create in the console as well—an API Key for the client. This is needed whether we use Nearby Messages or not. In the last post, we only created credentials for OAuth to use the management functions. To create an API key:

  1. In the console, select APIs & auth > Credentials

  2. Ensure the Credentials tab is selected, and click Add credentials > API key. Choose the client platform that fits. We will continue to use Android as the example.

    API Key Create Screen

  3. Give the key a name, and click Add package name and fingerprint

  4. Enter the package name of your client application, and the SHA-1 hash of the key used to sign the APK.

  5. Click Create

  6. Copy your new key to a safe place for later...

Reminder: This key must be attached to the same console project where you enabled the Proximity Beacon API!

Wire up Play Services

Adding Google Play Services to your application adds a bit of additional boilerplate that is well documented, so I will skip over the details except for what is relevant to Nearby Messages.

First, we need to add our Nearby Messages API key to the AndroidManifest.xml:

<application
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher"
  ... >

  <!-- API Key Generated by your Developers Console Project -->
  <meta-data
    android:name="com.google.android.nearby.messages.API_KEY"
    android:value="YOUR_KEY_HERE" />

  ...

</application>

Next, we need to create a GoogleApiClient to attach to Play Services. This client defines that we want to use the Nearby.MESSAGES_API:

private GoogleApiClient mGoogleApiClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    

    //Construct a connection to Play Services
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(Nearby.MESSAGES_API)
            .build();
}

@Override
protected void onStart() {
    super.onStart();
    //Initiate connection to Play Services
    mGoogleApiClient.connect();
}

This is an asynchronous process that, once completed, will trigger the client's ConnectionCallbacks instance:

@Override
public void onConnected(Bundle bundle) {
    // ...
}

This connection should only remain active while we are in the foreground:

@Override
protected void onStop() {
    super.onStop();
    //Tear down Play Services connection
    if (mGoogleApiClient.isConnected()) {
        mGoogleApiClient.disconnect();
    }
}

Now we can move on to the interesting pieces...

Subscribing to Beacon Messages

Once we are connected to Play Services, we can attempt to subscribe to incoming messages. However, Nearby Messages requires the user to grant your application permission to use the service at runtime. We have to verify they've allowed us first:

@Override
public void onConnected(Bundle bundle) {
    //Once connected, we have to check that the user has opted in
    Runnable runOnSuccess = new Runnable() {
        @Override
        public void run() {
            //Subscribe once user permission is verified
            subscribe();
        }
    };
    ResultCallback callback =
            new ErrorCheckingCallback(runOnSuccess);
    Nearby.Messages.getPermissionStatus(mGoogleApiClient)
            .setResultCallback(callback);
}

//ResultCallback triggered when to handle Nearby permissions check
private class ErrorCheckingCallback implements ResultCallback {
    private final Runnable runOnSuccess;

    private ErrorCheckingCallback(@Nullable Runnable runOnSuccess) {
        this.runOnSuccess = runOnSuccess;
    }

    @Override
    public void onResult(@NonNull Status status) {
        if (status.isSuccess()) {
            Log.i(TAG, "Permission status succeeded.");
            if (runOnSuccess != null) {
                runOnSuccess.run();
            }
        } else {
            // Currently, the only resolvable error is that the device is not opted
            // in to Nearby. Starting the resolution displays an opt-in dialog.
            if (status.hasResolution()) {
                try {
                    status.startResolutionForResult(MainActivity.this,
                            REQUEST_RESOLVE_ERROR);
                } catch (IntentSender.SendIntentException e) {
                    showToastAndLog(Log.ERROR, "Request failed with exception: "+e);
                }
            } else {
                showToastAndLog(Log.ERROR, "Request failed with : " + status);
            }
        }
    }
}

If the user has not granted permission yet, they will see a Play Services dialog when startResolutionForResult() is executed:

Nearby Messages Permission Dialog

Note: If you were wondering why I chose onStart() and onStop() instead of onPause() and onResume() above, this is why. That use dialog will pause the activity, but not stop it. This keeps our logic from running through a second (extra) time.

Once we have verified permission, we can safely subscribe for message events:

private void subscribe() {
    Log.d(TAG, "Subscribing…");
    Nearby.Messages.subscribe(
            mGoogleApiClient,
            mMessageListener,
            Strategy.BLE_ONLY);
}

MessageListener mMessageListener = new MessageListener() {
    // Called each time a new message is discovered nearby.
    @Override
    public void onFound(Message message) {
        Log.i(TAG, "Found message: " + message);
    }

    // Called when the publisher (beacon) is no longer nearby.
    @Override
    public void onLost(Message message) {
        Log.i(TAG, "Lost message: " + message);
    }
};

The MessageListener callbacks are NOT triggered on the main thread. You can't touch the UI from here directly.

For every observed beacon advertisement matching the supplied API key, the MessageListener will receive an onFound() callback where the Message represents the attachments associated with that beacon. The Message object consists of a namespace, type, and content field. I'll bet you'll be shocked to learn that they map directly to the namespacedType and data fields of each beacon attachment! Your application will be delivered all attachments, so you will need to manually filter on the type field if there are certain messages you don't want to keep.

When we are no longer in the foreground, we must unsubscribe from the messages API:

@Override
protected void onStop() {
    super.onStop();
    //Tear down Play Services connection
    if (mGoogleApiClient.isConnected()) {
        Log.d(TAG, "Un-subscribing…");
        Nearby.Messages.unsubscribe(
                mGoogleApiClient,
                mMessageListener);

        mGoogleApiClient.disconnect();
    }
}

Note: You can see the full source code for the messages subscriber in MainActivity.

Background Scanning

UPDATE 12/18/15: Google Play Services 8.4 and later now support beacon scanning in the background.

For applications targeting Google Play Service 8.4 and later. We can attach the same message processing behavior to a background scanning operation. To add background subscription for BLE messages, we amend the previous subscription code to look like the following:

private void subscribe() {
    Log.d(TAG, "Subscribing…");
    SubscribeOptions options = new SubscribeOptions.Builder()
            .setStrategy(Strategy.BLE_ONLY)
            .build();
    //Active subscription for foreground messages
    Nearby.Messages.subscribe(mGoogleApiClient,
            mMessageListener, options);

    //Passive subscription for background messages
    Intent serviceIntent = new Intent(this, BeaconService.class);
    PendingIntent trigger = PendingIntent.getService(this, 0,
            serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    ResultCallback callback = new BackgroundRegisterCallback();
    Nearby.Messages.subscribe(mGoogleApiClient, trigger, options)
            .setResultCallback(callback);
}

You can see the updated source code for the subscriber in the MainActivity.

The advantage of this approach is two-fold:

  1. We can now point the callback at a Service instead of an Activity.
  2. Since the callback is a PendingIntent, it can trigger the application even if it is not currently running.

When a background event is triggered, it will come to us as an intent. The Nearby.Messages class also now comes with a handy converter to take the incoming intent and process the result through the same MessageListener interface we saw before:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null) {
        Nearby.Messages.handleIntent(intent, mMessageListener);
    }

    return START_NOT_STICKY;
}

private MessageListener mMessageListener = new MessageListener() {
    @Override
    public void onFound(Message message) {
        // ...
    }

    @Override
    public void onLost(Message message) {
        // ...
    }
};

You can see the full source code for the background event handler in the BeaconService.

There's Always a Catch...

The latest docs on background scanning state the following:

When your app subscribes to beacon messages in the background, low-power scans are triggered at screen-on events, even when your app is not currently active.

Well...that means exactly what it says. Background scanning doesn't mean you will see a beacon event while the device is sitting in your user's pocket. You won't even while see one while they are actively using their device in another application. The only trigger will be when the screen does from OFF to ON.

As with all "limitations" of the Play Services API, this could drastically change in the next version. One never knows (well, somebody knows. But they ain't talkin'). Regardless, this is a welcome step in the right direction when it comes to making beacons technology more useful through Google's APIs.

Background Scans with Earlier Play Services

What follows is the original posting indicating how to execute background scans with versions of Play Service prior to the 8.4 SDK.

So far, this has pretty much felt like magic. But there is one glaring problem when it comes to beacon applications that I have side-stepped to this point. To quote the Nearby Messages guidelines:

On Android, only invoke the Nearby Messages API from an Activity, and only keep that Activity running when the screen is on and your app is in the foreground. To do this, pass the Activity (or FragmentActivity) as an argument to the Context parameter of GoogleApiClient.Builder(). Invoking Nearby from a Service is not supported.

Yikes! Don't even try background scanning with this API, because it won't work. This API is intended for foreground use only, which is not typical for beacon applications. Beacon applications typically want to push context to their users—indicating to them as soon as they are near something interesting. Since Nearby won't help us, we'll have to do a little extra work and make our own background scanner.

Up to this point, we've paid little attention to the beacon types because the Proximity Beacon API abstracts that away from us. However, moving forward we are assuming that the beacons in the field are all Eddystone. The sample scanning code assumes this to simplify the logic.

I'm also assuming the API 21+ Bluetooth LE APIs because it makes life easier, but you can accomplish the same thing back to API 18 with a little extra code.

// Eddystone service uuid (0xfeaa)
private static final ParcelUuid UID_SERVICE =
        ParcelUuid.fromString("0000feaa-0000-1000-8000-00805f9b34fb");

private static final String[] NAMESPACE_IDS = {};

private BluetoothLeScanner mBluetoothLeScanner;

/* Begin scanning for Eddystone advertisers */
private void startScanning() {
    List filters = new ArrayList<>();
    //Filter on just our requested namespaces
    for (String namespace : NAMESPACE_IDS) {
        ScanFilter beaconFilter = new ScanFilter.Builder()
                .setServiceUuid(UID_SERVICE)
                .setServiceData(UID_SERVICE, getNamespaceFilter(namespace),
                        NAMESPACE_FILTER_MASK)
                .build();
        filters.add(beaconFilter);
    }

    //Run in background mode
    ScanSettings settings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .build();

    mBluetoothLeScanner.startScan(filters, settings, mScanCallback);
}

/* Terminate scanning */
private void stopScanning() {
    mBluetoothLeScanner.stopScan(mScanCallback);
}

This scan runs in low power mode, so it is safe to run from a background service long-term. Upon each successful ScanCallback.onScanResult(), you can choose to post a notification to the user that a beacon is nearby. When they decide to engage with that notification, launch the activity to subscribe to Nearby Messages again and get the data you really want from it.

You can see the full source code for this scanner in the EddystoneScannerService.

Did You Ask Nicely?

On API 23+, retrieving Bluetooth LE scan results requires the location permission (in addition to the existing Bluetooth permissions)—which is a runtime granted permission. So we need to ask the user to allow us location access before attempting to do any background scanning. First, let's make sure our manifest has the correct declarations:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.nearbybeacons">

    <!-- Not required for Nearby, but needed for background scan -->
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <!-- Necessary to get scan results on API 23+ -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    ...
</manifest>

With ActivityCompat (part of the support library) v23+, asking for permission is fairly straightforward and doesn't require us to branch the code to support older API levels:

//The location permission is required on API 23+ to obtain BLE scan results
int result = ActivityCompat
        .checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
if (result != PackageManager.PERMISSION_GRANTED) {
    //Ask for the location permission
    ActivityCompat.requestPermissions(this,
            new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},
            REQUEST_PERMISSION);
}

Android 6.0+ devices will react to this by showing the user a permission dialog, whereas on all other platform versions this will immediately return PERMISSION_GRANTED.

Summary

Nearby Messages has us in an interesting spot at the moment. On the one hand, the Proximity Beacon API opens up really cool possibilities and having the client-side API reduces developer pain in getting what they really want from a beacon (contextual application-specific data). However, the lack of background capability forces us to augment the API quite a bit to ensure that our users get a simple experience when coming in contact proximity beacons.

In the final installment of this series, we will return to the Proximity Beacon API and learn how we can add some beacon health monitoring to our management console.