Bluetooth Low Energy (BLE) Beacons are one-way transmitters that mark important places and objects in a way that users' devices understand. The Google beacon platform provides a set of resources and APIs to make interacting with beacons power-efficient and useful. This allows developers to create context aware and proximity based applications using the high quality signal provided by BLE beacons.

Location and Proximity Superpowers: Eddystone + Google Beacon Platform - Google I/O 2016

In this codelab you'll create a simple app that uses the Nearby Messages API to fetch beacon attachments, blobs of useful data associated with beacons in the cloud. Along the way, you'll learn about beacons, the Eddystone beacon format, the Google beacon platform, and pick up tips on how to provision beacons and register them with Google. Nearby takes care of beacon scanning for you, so that you can focus on attachment data (the meaning you associated with a beacon) and building awesome features!

Workflow

Creating a simple, useable beacon-aware app of the kind we'll build in this codelab can be broken down into these steps:

  1. Doing basic setup of the beacon including the Eddystone-UID it broadcasts (sometimes called ‘provisioning' the beacon) using the beacon manufacturer's instructions.
  2. Using Google's Beacon Tools to register the beacon with the Google beacon platform under your Google Cloud Platform project. This step provides an abstraction between the beacon's ID and information associated with the beacon, making it possible for a single beacon to be used in different apps, services, and features.
  3. Associating useful information with your beacon by adding beacon attachments using Proximity Beacon API, Beacon Tools or the Beacon Dashboard.
  4. Writing an app that fetches beacon attachments using the Nearby Messages API.

Audience and Prerequisites

This codelab is intended for Android developers who want to learn about beacons, understand how to set up beacons, and write an context-aware apps that subscribes to and fetches attachments on those beacons.

No prior knowledge of beacons, the Google beacon platform, or Nearby Messages is required. But we do assume the following:

  1. Basic familiarity of writing Android applications.
  2. Access to an Android device. Location and Bluetooth must be enabled on the device.

Download the Code

You can clone the repo that contains the code used in this codelab:

$ git clone https://github.com/googlecodelabs/hello-beacons.git

Or you can download the code by clicking the following button and unpacking the contents:

Download source code

Change into the root directory and you'll see that the code is organized in the following sub-directories:

  1. HelloBeacons-Start
  2. HelloBeacons-Nearby-Setup
  3. HelloBeacons-Subscribe
  4. HelloBeacons-Display-Messages
  5. Hello-Beacons-Finished

Setting up Android Studio

Launch Android Studio by clicking the Studio icon:

Select the Import project (Eclipse ADT, Gradle, etc.) option:

Navigate to the location where you unzipped the source, and select HelloBeacons-Start.

Your Android studio screen should now look like this:

Expose the directory structure of the application using the following steps:

  1. Expand app/ in left column.
  2. Expand java/ and res/.
  3. Under res/, expand layout/.

All source files are located under app/java, and all layout files under app/res/layout.

Launching the HelloBeacons application

To launch the HelloBeacons app, first make sure your device is connected to the computer. Then, press the green Play icon from the menu bar towards the top of the screen.

The app has only a single activity, MainActivity, and if everything goes well, the app should open with MainActivity visible.

Here's a list of files that you'll be modifying in this codelab:

  1. MainActivity.java: the code for the only activity in the app.
  2. BackgroundSubscribeIntentService.java: the IntentService that handles messages delivered by Nearby Messages API to your app. Currently empty.
  3. Utils.java: utility methods used to display data scanned from beacons. Also currently empty.

Here are the contents of MainActivity.java:

MainActivity.java

public class MainActivity extends AppCompatActivity {

   private static final String TAG = MainActivity.class.getSimpleName();

   private GoogleApiClient mGoogleApiClient;

   private RelativeLayout mContainer;

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

       setContentView(R.layout.activity_main);
       Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
       setSupportActionBar(toolbar);

       mContainer = (RelativeLayout) findViewById(R.id.main_activity_container);
   }

   @Override
   protected void onResume() {
       super.onResume();
   }

   @Override
   protected void onPause() {
       super.onPause();
   }

   @Override
   protected void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
   }
}

The code is pretty barebones at the moment and it contains the following:

  1. Some Activity lifecycle methods (onCreate(), onResume(), onPause(), and onSaveInstanceState()). They're mostly empty now, but you'll content to them as we go through the codelab.
  2. The mContainer field, which holds a reference to the View which we'll later use to display messages from beacons.
  3. The mGoogleApiClient field, which holds a reference to GoogleApiClient, the main entry point to Google Play Services integration. Before we can use Nearby Messages API to subscribe to beacon attachments from BLE beacons, we need to set up GoogleApiClient.

In this step, we'll set up GoogleApiClient and implement the logic to connect and disconnect GoogleApiClient.

Modify MainActivity so that it implements GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener:

MainActivity.java

public class MainActivity extends AppCompatActivity
       implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

    ...
}

GoogleApiClient.ConnectionCallbacks provides callbacks that are called when GoogleApiClient connects or disconnects. Because MainActivity now implements GoogleApiClient.ConnectionCallbacks, you need to implement the onConnected(Bundle) and onConnectionSuspended(int) callbacks.

GoogleApiClient.OnConnectionFailedListener provides callbacks for handling scenarios where the GoogleApiClient connection fails. Because MainActivity now implements GoogleApiClient.OnConnectionFailedListener, you need to implement the onConnectionFailed(ConnectionResult) callback.

Add the needed callbacks to MainActivity.java:

MainActivity.java

public class MainActivity extends AppCompatActivity
       implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   ...

   @Override
   protected void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
   }

   @Override
   public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
       if (mContainer != null) {
           Snackbar.make(mContainer, 
                   "Exception while connecting to Google Play services: " +
                           connectionResult.getErrorMessage(),
                   Snackbar.LENGTH_INDEFINITE).show();
       }
   }

   @Override
   public void onConnectionSuspended(int i) {
       Log.w(TAG, "Connection suspended. Error code: " + i);
   }

   @Override
   public void onConnected(@Nullable Bundle bundle) {
       Log.i(TAG, "GoogleApiClient connected");
   }
}

The implementation of these callbacks is pretty minimal:

You'll be using the Nearby.Messages.subscribe() method to set up the subscription that allows HelloBeacons to scan for beacons. The Nearby.Messages.subscribe() method requires a connected GoogleApiClient, and you'll be calling that method later in this codelab inside onConnected().

Building GoogleApiClient and enableAutoManage()

With the ConnectionCallbacks and the OnConnectionFailedListener callbacks out of the way, we can now start building GoogleApiClient. Add the following method to MainActivity:

MainActivity.java

public class MainActivity extends AppCompatActivity
       implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   ...

   private synchronized void buildGoogleApiClient() {
       if (mGoogleApiClient == null) {
           mGoogleApiClient = new GoogleApiClient.Builder(this)
                   .addApi(Nearby.MESSAGES_API, new MessagesOptions.Builder()
                       .setPermissions(NearbyPermissions.BLE).build())
                   .addConnectionCallbacks(this)
                   .enableAutoManage(this, this)
                   .build();
       }
   }
}

We won't use this method yet (that part comes up soon in the next step), but there are a few things to note:

MainActivity.java should now look like this:

MainActivity.java

public class MainActivity extends AppCompatActivity
       implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   private static final String TAG = MainActivity.class.getSimpleName();

   private GoogleApiClient mGoogleApiClient;

   private RelativeLayout mContainer;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
      
       setContentView(R.layout.activity_main);
       Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
       setSupportActionBar(toolbar);

       mContainer = (RelativeLayout) findViewById(R.id.main_activity_container);
   }

   @Override
   protected void onResume() {
       super.onResume();
   }

   @Override
   protected void onPause() {
       super.onPause();
   }

   @Override
   protected void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
   }

   @Override
   public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
       if (mContainer != null) {
           Snackbar.make(mContainer,
                   "Exception while connecting to Google Play services: " +
                           connectionResult.getErrorMessage(),
                   Snackbar.LENGTH_INDEFINITE).show();
       }
   }

   @Override
   public void onConnectionSuspended(int i) {
       Log.w(TAG, "Connection suspended. Error code: " + i);
   }

   @Override
   public void onConnected(@Nullable Bundle bundle) {
       Log.i(TAG, "GoogleApiClient connected");
   }

   private synchronized void buildGoogleApiClient() {
       if (mGoogleApiClient == null) {
           mGoogleApiClient = new GoogleApiClient.Builder(this)
                   .addApi(Nearby.MESSAGES_API, new MessagesOptions.Builder()
                       .setPermissions(NearbyPermissions.BLE).build())
                   .addConnectionCallbacks(this)
                   .enableAutoManage(this, this)
                   .build();
       }
   }
}

Using the green Play icon from the Android Studio menu, run your program to make sure everything works as intended. There's nothing to display yet, so you'll still get an empty screen. But you should not see any error messages.

Close the current Android Studio project (go to File > Close Project).

The Nearby Messages API takes care of scanning for beacons for you, fetches beacon attachments and delivers them to your app as a message object. As well as an API in Google Play Services, there is a CocoaPod for iOS devices. You can specify the Namespace and Type of attachments that your app is interested in. By default, Nearby will fetch all beacon attachments associated with namespaces that your project owns. You can subscribe to attachments in others' namespaces, provided that those namespaces have appropriately set visibility.

Subscribe to BLE beacon messages

Nearby handles beacon scanning on your behalf, so that you don't have to schedule scans or think about beacon IDs when creating your subscription.. You can subscribe to fetch beacon attachments In the foreground (in response to a user action or event) or in the background (when your app is not running). Foreground subscriptions maintain continuous beacon scans, which can affect users' battery if overused. Nearby handles background beacon scanning in a battery efficient way, waking your app only when an interesting beacon is close by, and you'll be using this method in HelloBeacons.

When your app subscribes to beacon messages in the background, low-power scans are triggered at screen-on events, or when the screen is off if other services are performing scans, even when your app is not currently active. You will use these scan notifications to "wake up" your app in response to a particular message.

The full Nearby Messages API can be used for a variety of device-to-device interactions, as well as beacon scanning. For this codelab, you can initiate a beacon-only background subscription by calling Nearby.Messages.subscribe() and setting the Strategy option to BLE_ONLY. Before creating a subscription, we need to make sure that the user has granted all the necessary permissions.

Using the Nearby Messages API requires user consent. Prior to subscribing, your app should check to see whether the user has consented, and present them with a consent dialog if they have not.

Using the Import Project (Eclipse ADT, Gradle, etc.), open HelloBeacons-Nearby-Setup. The code in this step picks up where Step 1 left off.

Permissions in the Manifest

Using the Nearby Messages API requires user consent. Open app/src/main/AndroidManifest.xml to see the permissions necessary for HelloBeacons to function:

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.google.android.gms.nearby.messages.samples.hellobeacons">

   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

  ...
</manifest>

The ACCESS_FINE_LOCATION permission is required and Nearby provides a handy permissions dialog for getting the user's consent. You'll implement the code that allows the user to grant permissions to HelloBeacons using this dialog.

Working with runtime permissions

In MainActivity.java, create a PERMISSIONS_REQUEST_CODE constant. We'll use this constant as part of the permissions workflow for Nearby:

MainActivity.java

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   private static final String TAG = ...

   private static final int PERMISSIONS_REQUEST_CODE = 1111;

   ...

}

Add the havePermissions() and requestPermissions() methods to MainActivity:

MainActivity.java

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

    ...

  private synchronized void buildGoogleApiClient() {
    ...
  }

   private boolean havePermissions() {
       return ContextCompat.checkSelfPermission(this,     Manifest.permission.ACCESS_FINE_LOCATION)
               == PackageManager.PERMISSION_GRANTED;
   }

   private void requestPermissions() {
       ActivityCompat.requestPermissions(this,
               new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE);
   }

The havePermissions() method checks if you have been granted the ACCESS_FINE_LOCATION permission. The requestPermissions() method requests that permission.

Next, use these two methods in the activity lifecycle. Modify the onCreate() and the onResume() methods as shown:

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   ...

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

       mContainer = (RelativeLayout) findViewById(...);

       if (!havePermissions()) {
           Log.i(TAG, "Requesting permissions needed for this app.");
           requestPermissions();
       }
   }

   @Override
   protected void onResume() {
       super.onResume();
       if (havePermissions()) {
           buildGoogleApiClient();
       }
   }
   ...
  }

You check permission in onCreate() and request permissions if necessary. You check permissions again in onResume() in case the user has gone to Settings and granted permissions there (thus bypassing the Permissions Dialog flow).

Once the apps permissions requirements are met, you call buildGoogleApiClient(), which you implemented this earlier in the codelab. This kicks off the process of building and connecting GoogleApiClient.

Using the green Play icon from the Android Studio menu, run the app. Your screen should look this:

Don't press "Deny" or "Allow" just yet. We still have a little more work to do. If you accidentally pressed "Allow", delete the app from the device. That way, you'll be forced to go through the permission workflow all over again.

Handling permission flow

A user is presented with the Permissions dialog shown above, can take any of these three actions:

  1. Press "ALLOW". When this happens, you proceed with the main logic of the app.
  2. Press "DENY". You display a Snackbar that allows the user to once again display the Permissions Dialog.
  3. Press "DENY" with "Never Ask Again" checked. Since this stops the permissions dialog from being show again, you display a Snackbar directing the user to Settings to grant the location permission there.

Add the showLinkToSettingsSnackBar() and showRequestPermissionsSnackBar() methods to MainActivity.java. These methods are responsible for displaying Snackbars when a user does not grant permission:

MainActivity.java

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   ...

   private requestPermissions() {
      ...
   }

   private void showLinkToSettingsSnackbar() {
       if (mContainer == null) {
           return;
       }
       Snackbar.make(mContainer,
               R.string.permission_denied_explanation,
               Snackbar.LENGTH_INDEFINITE)
               .setAction(R.string.settings, new View.OnClickListener() {
                   @Override
                   public void onClick(View view) {
                       // Build intent that displays the App settings screen.
                       Intent intent = new Intent();
                       intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                       Uri uri = Uri.fromParts("package",
                               BuildConfig.APPLICATION_ID, null);
                       intent.setData(uri);
                       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                       startActivity(intent);
                   }
               }).show();
   }

   private void showRequestPermissionsSnackbar() {
       if (mContainer == null) {
           return;
       }
       Snackbar.make(mContainer, R.string.permission_rationale,
               Snackbar.LENGTH_INDEFINITE)
               .setAction(R.string.ok, new View.OnClickListener() {
                   @Override
                   public void onClick(View view) {
                       // Request permission.
                       ActivityCompat.requestPermissions(MainActivity.this,
                               new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                               PERMISSIONS_REQUEST_CODE);
                   }
               }).show();
   }
}

Handling user response

We have now handled the flow for the Permissions Dialog. Once a user engages with Dialog, the onRequestPermissionsResult() callback fires. Add the code for this callback to MainActivity.java:

MainActivity.java

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
       GoogleApiClient.OnConnectionFailedListener {

   ...

   @Override
   public void onRequestPermissionsResult(int requestCode, 
           @NonNull String[]permissions, @NonNull int[] grantResults) {
       if (requestCode != PERMISSIONS_REQUEST_CODE) {
           return;
       }
      
       for (int i = 0; i < permissions.length; i++) {
           String permission = permissions[i];
           if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
               if (shouldShowRequestPermissionRationale(permission)) {
                   Log.i(TAG, "Permission denied without 'NEVER ASK AGAIN': " 
                       + permission);
                   showRequestPermissionsSnackbar();
               } else {
                   Log.i(TAG, "Permission denied with 'NEVER ASK AGAIN': " 
                       + permission);
                   showLinkToSettingsSnackbar();
               }
           } else {
               Log.i(TAG, "Permission granted, building GoogleApiClient");
               buildGoogleApiClient();
           }
       }
   }
}

Note that once the user grants permissions, we call buildGoogleApiClient(), which triggers the following flow:

  1. GoogleApiClient builds
  2. GoogleApiClient automatically starts to connect
  3. Once GoogleApiClient connects, the onConnected() callback fires.

Using the green Play icon from the Android Studio menu, run the app. When the Permissions Dialog opens, press "Allow". Or, to test the workflow, press "Deny", retrigger the permissions flow by tapping "OK" on the Snackbar, and then press "Allow".

That's all for this step. We're done with all the boilerplate code related to permissions. In the next step we'll add code to the onConnected() callback to start scanning for beacons!

Close the current Android Studio project (go to File > Close Project).

Using the Import Project (Eclipse ADT, Gradle, etc.), open HelloBeacons-Subscribe.

In this step, you'll implement the functionality to subscribe to beacon attachments using Nearby Messages API. But for the background scanning to actually work, you need to add an API KEY in AndroidManifest.xml.

Adding an API KEY

Using the Nearby Messages API requires an API key to ensure that your app receives the right attachments.

Beacon attachments always have an associated Namespace and Type. Namespaces contain a visibility field that allows beacon deployers to control which other projects have access to the information associated with their beacons. By default, attachment Namespaces are ‘unlisted', meaning that attachment data is only returned for calls that contain an API key issued by the same project that owns the Namespace. Note that the API key is the only check for Proximity Beacon API's serving endpoints: no end-user credentials are involved. For this reason, do not store personal or sensitive information in beacon attachments.

If beacons are intended to be used in a public beacon network, attachment Namespace visibility can be set to ‘public', meaning that attachment data is returned for any request regardless of the API key provided. Nevertheless, an API key is required.

If you are registering beacons and adding attachments from scratch, you'll need to set up a project in Google API Console, and associate the beacons with that project. Then, you can generate an API KEY and enter that in the AndroidManifest.xml.

Subscribing and retaining state

In this codelab, we'll subscribe when MainActivity launches, but not every time the device rotates. We'll find a way to retain state, and when a subscription is active (i.e., a call to Nearby.Messages.subscribe() has succeeded), we won't bother replacing that subscription with a new one.

To track the subscription state, create a private boolean field in MainActivity.java:

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
    ...

    private boolean mSubscribed = false;

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
        }
        ...
}

Create a static variable that you'll use in saving the subscription state to the bundle:

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private static final int PERMISSIONS_REQUEST_CODE = ...;

    private static final String KEY_SUBSCRIBED = "subscribed";

    ...
}

To retain the state of the mSubscribed variable, add the following lines to onCreate():

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
   ...

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

        mContainer = ...;

        if (savedInstanceState != null) {
            mSubscribed = savedInstanceState.getBoolean(KEY_SUBSCRIBED, false);
        }

        if (!havePermissions()) {...}
    }
}

Then, to save the current value of mSubscribed in the Bundle, modify the onSaveInstanceState() method so it looks like this:

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
   ...

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(KEY_SUBSCRIBED, mSubscribed);
    }
}

Subscribing using Nearby Messages API

Add the subscribe(), getPendingIntnet(), and getBackgroundServiceIntent() method to MainActivity.java (we'll talk about what these methods do in a moment):

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
   ...
    private void subscribe() {
        if (mSubscribed) {
            Log.i(TAG, "Already subscribed.");
            return;
        }

        SubscribeOptions options = new SubscribeOptions.Builder()
                .setStrategy(Strategy.BLE_ONLY)
                // Note: If no filter is specified, Nearby will return all of your
                // attachments regardless of type. You must use a filter to specify
                // a particular set of attachments (by type) or to fetch attachments
                // in a namespace other than your project's default.
                .setFilter(new MessageFilter.Builder()
                    .includeNamespacedType("some_namespace", "some_type")
                .build();

        Nearby.Messages.subscribe(mGoogleApiClient, getPendingIntent(), options)
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(@NonNull Status status) {
                        if (status.isSuccess()) {
                            Log.i(TAG, "Subscribed successfully.");
                            startService(getBackgroundSubscribeServiceIntent());
                        } else {
                            Log.e(TAG, "Operation failed. Error: " +
                                    NearbyMessagesStatusCodes.getStatusCodeString(
                                            status.getStatusCode()));
                        }
                    }
                });
    }

  private PendingIntent getPendingIntent() {
        return PendingIntent.getService(this, 0,
                getBackgroundSubscribeServiceIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private Intent getBackgroundSubscribeServiceIntent() {
        return new Intent(this, BackgroundSubscribeIntentService.class);
    }

    ...
}

The subscribe() method calls the Nearby.Messages.subscribe() method to set up a background subscription to scan for attachments from nearby beacons. The Nearby.Messages.subscribe() method takes three arguments:

  1. A connected GoogleApiClient. We learned how to set this up in the previous step.
  2. A PendingIntent. Nearby keeps track of the beacons being scanned by your device, and when it find a beacon that is yours, it wakes up your app to notify you of that (even if your app has been killed). Nearby uses this PendingIntent to relaunch your app.
  3. A SubscribeOptions object that contains the options for calling Nearby.Messages.subscribe(). There are many options that you can use when subscribing, but the two most important ones in our code are:
  1. defining the Strategy to tell Nearby only to look for BLE beacon attachments.
  2. setting a filter to fetch attachments from Namespaces other than your project's default, or to restrict to only fetch messages of a particular Type. If you intend to make use of a public beacon network then you should check with the administrator of the beacon network what Namespace and Type to use for your particular application.
SubscribeOptions options = new SubscribeOptions.Builder()
    .setStrategy(Strategy.BLE_ONLY)
    .setFilter(new MessageFilter.Builder()
        .includeNamespacedType("some_namespace", "some_type")
    .build();

Now, use the subscribe() method you just created by modifying the code in the onConnected() callback:

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
   ...

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Log.i(TAG, "GoogleApiClient connected");
        subscribe();
    }
}

Knowing the subscription status

We attached a ResultCallback to the Nearby.Messages.subscribe() call and Nearby will let us know if you subscribed successfully or not.

Using the green Play icon from the Android Studio menu, run the app.

If everything goes as planned, you should see a "Subscribed successfully" message in the logs. If the subscription failed, you should see an error message.

Close the current Android Studio project (go to File > Close Project).

In the previous step, you called Nearby.Messages.subscribe(), using a PendingIntent as one of the arguments. Nearby uses that PendingIntent to inform your app inside BackgroundSubscribeIntentService about nearby beacon attachments. When Nearby detects a new beacon, it reads the beacon attachment and transmits that attachment to your app as Nearby Message. When Nearby detects that a previously scanned beacon is no longer in range, it also informs your app that that message has been lost.

You are now ready to write the code to process the beacon scans that Nearby reports to your app.

Using the Import Project (Eclipse ADT, Gradle, etc.), open HelloBeacons-Display-Messages.

Open BackgroundSubscribeIntentService.java, and modify the onHandleIntent() method so it looks like this:

BackgroundSubscribeIntentService.java

public class BackgroundSubscribeIntentService extends IntentService {

    ...


    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            Nearby.Messages.handleIntent(intent, new MessageListener() {
                @Override
                public void onFound(Message message) {
                    Log.i(TAG, "found message = " + message);
                }

                @Override
                public void onLost(Message message) {
                    Log.i(TAG, "lost message = " + message);
                }
            });
        }
    }
}

You call Nearby.Messages.handleIntent() to process the information sent to you by Nearby, using a MessageListener object. There are two important methods whose function you should understand:

  1. When Nearby reports that it has found a beacon, the onFound() method gets called.
  2. When Nearby reports that it can no longer track a beacon it had previously found (probably because the beacon is no longer in range), the onLost() method gets called.

The Message that Nearby transmits is available inside onFound() and onLost().

Using the green Play icon from the Android Studio menu, run your app.

Look in the logs to make sure you subscription worked, and wait for the log messages from BackgroundSubscribeIntentService to tell you if any beacons were found.

Close the current Android Studio project (go to File > Close Project).

If you succeeded in scanning for beacon attachments in the previous step, your app already works. Congratulations!!!

In this step, we'll spruce up the UI a bit and display beacon attachments found by Nearby in a list on MainActivity. We'll also add a persistent notification so that you can know about scanned beacons even when your app is not in use (or even when you screen is locked).

Using the Import Project (Eclipse ADT, Gradle, etc.), open HelloBeacons-Display-Messages.

New functionality and a better user experience

In the previous step, we logged the beacon attachment provided to HelloBeacons in BackgroundSubscribeIntentService.

  1. In this step, we'll write all messages to a list that we persist in SharedPreferences. When a new beacon attachment message is found, we'll add it to the list, and when it is lost, we'll remove it from the list.
  2. In MainActivity, we'll set up a ListView and ArrayAdapter to display all the messages.
  3. We'll add a persistent notification that will inform the user about new messages. That way, when beacon attachments are scanned by Nearby, the results will be presented to the user even if the app is not in use.
  4. The user can tap on the persistent notification to launch MainActivity.

Here's a summary and survey of the new code you'll discover in HelloBeacons-Display-Messages:

In Utils.java, we've added code for getting messages that were saved to SharedPreferences, and saving a found message and removing a lost message from SharedPreferences.

public final class Utils {
    ...
    static List<String> getCachedMessages(...) {
        ...
    }
    static void saveFoundMessage(...) {
        ...       
    }

    static void removeLostMessage(...) {
        ...
    }

    static SharedPreferences getSharedPreferences(Context context) {
        ...
    }
}

In BackgroundSubscribeIntentService.java, we've added code to manage the persistent notification, and to formulate it nicely with a title and body:

    private void updateNotification() {
        ...
    }

    private String getContentTitle(List<String> messages) {
        ...
    }

    private String getContentText(List<String> messages) {
        ...
    }

We've made numerous changes to MainActivity.java to display the attachment messages.

We've added an ArrayAdapter and a backing ArrayList to store messages from Nearby.

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        SharedPreferences.OnSharedPreferenceChangeListener {
    
    ...

    private ArrayAdapter<String> mNearbyMessagesArrayAdapter;

    private List<String> mNearbyMessagesList = new ArrayList<>();

    ...
}

MainActivity.java now implements SharedPreferences.OnSharedPreferenceChangeListener and we call registerOnSharedPreferenceChangeListener(this) in onResume() and unregisterOnSharedPreferenceChangeListener(this) in onPause(). In the onSharedPreferenceChanged() callback, we've added code to display the list of messages found by Nearby. This callback is triggered every time the app adds or removes a message to SharedPreferences, and when that happens, we update the list of messages and ask the ArrayAdapter to refresh the contents on the screen.

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (TextUtils.equals(key, Utils.KEY_CACHED_MESSAGES)) {
            mNearbyMessagesList.clear();
            mNearbyMessagesList.addAll(Utils.getCachedMessages(this));
            mNearbyMessagesArrayAdapter.notifyDataSetChanged();
        }
    }

Stitching it all together

You need to make only a few small changes to complete the new UI for displaying messages.

In MainActivity.java, add the following lines at the bottom of the onCreate() method:

MainActivity.java

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        SharedPreferences.OnSharedPreferenceChangeListener {

   . . .

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

        final List<String> cachedMessages = Utils.getCachedMessages(this);
        if (cachedMessages != null) {
            mNearbyMessagesList.addAll(cachedMessages);
        }

        final ListView nearbyMessagesListView = (ListView) findViewById(
                R.id.nearby_messages_list_view);
        mNearbyMessagesArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
                mNearbyMessagesList);
        if (nearbyMessagesListView != null) {
            nearbyMessagesListView.setAdapter(mNearbyMessagesArrayAdapter);
        }
    }

    . . .
}

In BackgroundSubscribeIntenentService.java, find the onHandleIntent() method and modify the code inside onFound() and onLost() to save content to SharedPreferences and update the persistent notification:

public class BackgroundSubscribeIntentService extends IntentService {

    . . .

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            Nearby.Messages.handleIntent(intent, new MessageListener() {
                @Override
                public void onFound(Message message) {
                    Utils.saveFoundMessage(getApplicationContext(), message);
                    updateNotification();
                }

                @Override
                public void onLost(Message message) {
                    Utils.removeLostMessage(getApplicationContext(), message);
                    updateNotification();
                }
            });
        }
    }
}

Using the green Play icon from the Android Studio menu, run your app.

Your screen should now look something like this:

In this codelab, we assumed that beacons had already been provisioned, registered with the beacon platform and had attachments added. If you want to do all that yourself, here are the steps:

  1. Create a Google Console Project at https://console.developers.google.com.
  2. Use the API key generated in 1) in the HelloBeacons manifest.
  3. Provision beacons using the manufacturer's instructions.
  4. Register the beacons with Google and add attachments using the Beacon Tools app for Android or iOS, Proximity Beacon API or the Beacon Dashboard.

Create a Google console project

Go to https://console.developers.google.com/ and select Create a project....

Remember the project name, which determines the default namespace for your attachments. When you register your beacons with Google, you'll be linking them to this project. Further, this project will own all the beacon and attachment data that you store in the service.

The API Manager screen

Open the side nav and select API Manager.

On the API Manager screen do the following:

  1. Search for "Nearby" in the search box, and "Nearby Messages API" should be presented to you.
  2. Click on "Nearby Messages API" and press the blue Enable button.

You'll be asked to set up credentials.

Credentials screen

Click on "Go to Credentials" and on the Credentials screen, do the following:

  1. For the "Which API are you using", pick "Nearby Messages API".
  2. For the "Where will you be calling the API from?", pick "Android".

  1. Click on the blue What credentials do I need button to create an API Key.
  2. Click on "Add package name and fingerprint".
  3. Enter "com.google.android.gms.nearby.messages.samples.hellobeacons" for the package name.
  4. To get SHA-1 certificate fingerprint, type the following in a terminal:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

  1. Enter the SHA-1 value in the form.
  2. Hit the Create button

Use the API Key

You should see a popup with your API Key.

Open HelloBeacons-Finished in Android Studio and add the API key to AndroidManifest.xml.

Provision beacons

Beacons can be provisioned using instructions provided by the beacon manufacturer. Many vendors provide Android apps to help with provisioning. For example:

When provisioning your beacon, follow these general guidelines to set the beacon's key parameters (broadcast ID, advertising interval or broadcast rate, broadcast power):

  1. Pick the Eddystone format.
  2. Make sure Eddystone Beacon Type is set to UID.
  3. Make sure Advertisement Rate is not disabled.
  4. Make sure the Connection Rate is not blank.

Register beacons with Google using Beacons Tools

The Google Beacon Tools app, which lets you register your beacons with the Google Beacon Registry and create small attachments for them.

With the beacon you have just registered in hand, launch Beacon Tools and go through the following steps:

  1. Open the SideDrawer and make sure Current Project is set to the project you created at https://console.developers.google.com/
  2. Close the SideDrawer. You're on the Beacons Near Me page and Beacon Tools shows you beacons it can find. You should see the beacon you just provisioned in the UNREGISTERED column (it helps to place the beacon close to your phone so it gets picked up quickly).
  3. Click on the listing for your beacon. This takes you to the Registering Beacons screen. You can use this page to configure many details about your beacon.
  4. Click on Attachments to go to the screen for adding an attachment. (You can use the Beacon Dashboard to add attachments too.)You'll see two fields:
  1. Namespaced Type: this displays your project's namespace (which is the same as the project ID), followed by a "/" then the Type field. The Type field is intended to help you distinguish between different categories of attachments. Following the "/", add "important_object": you can choose whatever you like for the Type, including for example particular features of your app or your dog's name.
  2. Data: add the attachment data here. Beacon Tools will base64-encode the attachment for you.

  1. Press the Check button on the top right.
  2. This takes you to the Registering Beacons screen.
  3. Click on Register Beacon.

Run the app!

Using the green Play icon from the Android Studio menu, run HelloBeacons.

HelloBeacons should be able to find the beacons you set up, and it should display the attachment data on the screen.