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:
- In the console, select APIs & auth > APIs
- Search for Nearby Messages API and select it
- 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:
-
In the console, select APIs & auth > Credentials
-
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.
-
Give the key a name, and click Add package name and fingerprint
-
Enter the package name of your client application, and the SHA-1 hash of the key used to sign the APK.
-
Click Create
-
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:
Note: If you were wondering why I chose
onStart()
andonStop()
instead ofonPause()
andonResume()
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:
- We can now point the callback at a
Service
instead of anActivity
. - 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.