Exploring Nearby Connections 2.0

Google recently announced the new API for Nearby Connections, which greatly improves upon the the first version by having offline support, higher-bandwidth operations and lower latency. This new API uses a combination of WiFi hotspots, Bluetooth Low Energy & Classic Bluetooth to discover and advertise to nearby devices. But developers don’t need to worry about the complexities of managing Bluetooth and Wifi connections, as it’s all abstracted out under the hood. In this post I wanted to lay out what building an app with the new API would look like.

For example, a Bluetooth connection may have a lower connection latency, but it also provides lower bandwidth support. On the other hand, a connection made through WiFi hotspots would have a higher connection latency, but also be able to allow higher bandwidth support. The API leverages the strengths of each type of connection to optimize for certain situations. On the user-facing side, users will not be prompted to turn on Bluetooth or Wifi, as the system would enable these features when necessary and then restore the device state once the operations have been finished.

To try out the new API, I decided to build an app that would allow a teacher to track attendance. The basic idea is that students would walk into the classroom and be able to check themselves in and the teacher would never have to go through roll-call again. The teacher’s side of the app is the advertiser, sending notifications to student’s devices reminding them to check in once they are within proximity. The student’s side of the app is the discoverer, looking for the signal so they can mark themselves as present.

The first step was to install Google Play Services 11 and compile play-services-nearby in build.gradle

compile 'com.google.android.gms:play-services-nearby:11.0.4'

The app then has to request the necessary permissions to be allowed to use the Nearby Connections API. Since the system handles all the details on how connections are made, the user needs to grant all the permissions in order for this to work. Note that ACCESS_COARSE_LOCATION needs to be further requested at runtime since it’s categorized as a dangerous permission.

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses ... "android.permission.ACCESS_WIFI_STATE" />
<uses ... "android.permission.CHANGE_WIFI_STATE" />
<uses ... "android.permission.ACCESS_COARSE_LOCATION" />

Finally, we can start implementing the application code. In order for the app to start advertising itself, we have to first connect to the GoogleApiClient, and only once a successful connection has been made, can we start advertising.

public class AdvertiserActivity extends Activity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
...
@Override
public void
onConnected(@Nullable Bundle bundle) {
// GoogleApiClient is now connected, we can start advertising
startAdvertising();
}
// client's name that's visible to other devices when connecting
public static final String CLIENT_NAME = "Teacher";
/**
* service id. discoverer and advertiser can use this id to
* verify each other before connecting
* /
public static final String SERVICE_ID = "Class302";
/**
* The connection strategy we'll use.
* P2P_STAR denotes there is 1 advertiser and N discoverers
*

public static final String STRATEGY = Strategy.P2P_STAR;
/**
* Set device to advertising mode by broadcasting to other
* devices that are currently in discovery mode.
* /
private void startAdvertising() {
Nearby.Connections.startAdvertising(
mGoogleApiClient,
CLIENT_NAME,
SERVICE_ID,
mConnectionLifecycleCallback,
new AdvertisingOptions(STRATEGY))
.setResultCallback(
new ResultCallback<Connections.StartAdvertisingResult>() {
@Override
public void
onResult(@NonNull Connections.StartAdvertisingResult result) {
if (result.getStatus().isSuccess()) {
Log.i(TAG, "Advertising endpoint);
} else {
Log.i(TAG, "unable to start advertising);
}
}
});
}
/** 
* These callbacks are made when other devices:
* 1. tries to initiate a connection
* 2. completes a connection attempt
* 3. disconnects from the connection
* /
private final ConnectionLifecycleCallback mConnectionLifecycleCallback =
new ConnectionLifecycleCallback() {
@Override
public void
onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
Log.i(TAG, endpointId + " connection initiated");
establishConnection(endpointId);
}

@Override
public void
onConnectionResult(String endpointId, ConnectionResolution result) {
markStudentAsPresent(endpointId);
}

@Override
public void
onDisconnected(String endpointId) {
Log.i(TAG, endpointId + " disconnected");
}
};

In this case, I chose to start the advertising with the P2P_STAR strategy. This allows each device to be either a hub (which can accept connections) or a spoke (which can initiate a connection), but not both simultaneously. In other words, it’s an 1-to-N strategy. The other option would have been P2P_CLUSTER, where each device can be both a hub or a spoke simultaneously, creating a cluster-shaped connection.

On the discoverer side, we similarly to start the discovery process and perform the actions once we’ve successfully made a discovery.

private void startDiscovery() {
Nearby.Connections.startDiscovery(
mGoogleApiClient,
SERVICE_ID,
mEndpointDiscoveryCallback,
new DiscoveryOptions(STRATEGY))
.setResultCallback(
new ResultCallback<Status>() {
@Override
public void
onResult(@NonNull Status status {
if (status.isSuccess()) {
Log.i(TAG, "Now looking for advertiser");
} else {
Log.i(TAG, "Unable to start discovery");
}
}
});
}
/** 
* These callbacks are made when :
* 1. an endpoint that we can connect to is found
* 2. completes a connection attempt
* /

private final EndpointDiscoveryCallback mEndpointDiscoveryCallback =
new EndpointDiscoveryCallback() {
@Override
public void
onEndpointFound(
String endpointId, DiscoveredEndpointInfo dei) {
requestConnection();
}

@Override
public void
onEndpointLost(String endpointId) {
// A previously discovered endpoint has gone away,
// perhaps we might want to do some cleanup here
Log.i(TAG, endpointId + " endpoint lost):
}
};

Once a connection has been established between two devices, they can start sending each other payloads in the form of bytes, files, or stream (for audio/video).

The sequence of events that takes place from the time one device starts the advertisement, to establishing a connection with another device, and finally to the connection terminating is neatly laid out in this diagram found in the Google IO session ‘How to Enable Contextual App Experiences”

Screenshot taken from Google IO session : How to Enable Contextual App Experiences

In future posts, I’m hoping to explore how a teacher might be able to send her students assignments through this app using the Nearby Connections API.