This lesson teaches you to
When you make calls with the data layer, you can receive the status of the call when it completes as well as listen for any changes that the call ends up making with listeners.
Wait for the Status of Data Layer Calls
You'll notice that calls to the data layer API sometimes return a
PendingResult
,
such as
putDataItem()
.
As soon as the PendingResult
is created,
the operation is queued in the background. If you do nothing else after this, the operation
eventually completes silently. However, you'll usually want to do something with the result
after the operation completes, so the
PendingResult
lets you wait for the result status, either synchronously or asynchronously.
Asynchronously waiting
If your code is running on the main UI thread, do not making blocking calls
to the data layer API. You can run the calls asynchronously by adding a callback
to the PendingResult
object,
which fires when the operation is completed:
pendingResult.setResultCallback(new ResultCallback<DataItemResult>() { @Override public void onResult(final DataItemResult result) { if(result.getStatus().isSuccess()) { Log.d(TAG, "Data item set: " + result.getDataItem().getUri()); } } });
Synchronously waiting
If your code is running on a separate handler thread in a background service (which is the case
in a WearableListenerService
),
it's fine for the calls to block. In this case, you can call
await()
on the PendingResult object, which will block until the request has completed, and return a Result
object:
DataItemResult result = pendingResult.await(); if(result.getStatus().isSuccess()) { Log.d(TAG, "Data item set: " + result.getDataItem().getUri()); }
Listen for Data Layer Events
Because the data layer synchronizes and sends data across the handheld and wearable, you normally want to listen for important events, such as when data items are created, messages are received, or when the wearable and handset are connected.
To listen for data layer events, you have two options:
- Create a service that extends
WearableListenerService
. - Create an activity that implements
DataApi.DataListener
.
With both these options, you override any of the data event callbacks that you care about handling in your implementation.
With a WearableListenerService
You typically create instances of this service in both your wearable and handheld apps. If you don't care about data events in one of these apps, then you don't need to implement this service in that particular app.
For example, you can have a handheld app that sets and gets data item objects and a wearable app that listens for these updates to update it's UI. The wearable never updates any of the data items, so the handheld app doesn't listen for any data events from the wearable app.
You can listen for the following events with
WearableListenerService
:
onDataChanged()
- Called when data item objects are created, changed, or deleted. An event on one side of a connection triggers this callback on both sides.onMessageReceived()
- A message sent from one side of a connection triggers this callback on the other side of the connection.onPeerConnected()
andonPeerDisconnected()
- Called when connection with the handheld or wearable is connected or disconnected. Changes in connection state on one side of the connection triggers these callbacks on both sides of the connection.
To create a WearableListenerService
:
- Create a class that extends
WearableListenerService
. - Listen for the events that you care about, such as
onDataChanged()
. - Declare an intent filter in your Android manifest to notify the system about your
WearableListenerService
. This allows the system to bind your service as needed.
The following example shows how to implement a simple
WearableListenerService
:
public class DataLayerListenerService extends WearableListenerService { private static final String TAG = "DataLayerSample"; private static final String START_ACTIVITY_PATH = "/start-activity"; private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received"; @Override public void onDataChanged(DataEventBuffer dataEvents) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onDataChanged: " + dataEvents); } final Listevents = FreezableUtils .freezeIterable(dataEvents); GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .build(); ConnectionResult connectionResult = googleApiClient.blockingConnect(30, TimeUnit.SECONDS); if (!connectionResult.isSuccess()) { Log.e(TAG, "Failed to connect to GoogleApiClient."); return; } // Loop through the events and send a message / to the node that created the data item. for (DataEvent event : events) { Uri uri = event.getDataItem().getUri(); // Get the node id from the host value of the URI String nodeId = uri.getHost(); // Set the data of the message to be the bytes of the URI. byte[] payload = uri.toString().getBytes(); // Send the RPC Wearable.MessageApi.sendMessage(googleApiClient, nodeId, DATA_ITEM_RECEIVED_PATH, payload); } } }
Here's the corresponding intent filter in the Android manifest file:
<service android:name=".DataLayerListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service>
Permissions within Data Layer Callbacks
In order to deliver callbacks to your application for data layer events, Google Play services
binds to your WearableListenerService
,
and calls your callbacks via IPC. This has the consequence
that your callbacks inherit the permissions of the calling process.
If you try to perform a privileged operation within a callback, the security check fails because your callback is running with the identity of the calling process, instead of the identity of your app's process.
To fix this, call clearCallingIdentity()
,
to reset identity after crossing the IPC boundary, and then restore identity with
restoreCallingIdentity()
when
you've completed the privileged operation:
long token = Binder.clearCallingIdentity(); try { performOperationRequiringPermissions(); } finally { Binder.restoreCallingIdentity(token); }
With a Listener Activity
If your app only cares about data layer events when the user is interacting with the app and does not need a long-running service to handle every data change, you can listen for events in an activity by implementing one or more of the following interfaces:
To create an activity that listens for data events:
- Implement the desired interfaces.
- In
onCreate(Bundle)
, create an instance ofGoogleApiClient
to work with the data layer API. -
In
onStart()
, callconnect()
to connect the client to Google Play services. - When the connection to Google Play services is established, the system calls
onConnected()
. This is where you callDataApi.addListener()
,MessageApi.addListener()
, orNodeApi.addListener()
to notify Google Play services that your activity is interested in listening for data layer events. - In
onStop()
, unregister any listeners withDataApi.removeListener()
,MessageApi.removeListener()
, orNodeApi.removeListener()
. - Implement
onDataChanged()
,onMessageReceived()
,onPeerConnected()
, andonPeerDisconnected()
, depending on the interfaces that you implemented.
Here's an example that implements
DataApi.DataListener
:
public class MainActivity extends Activity implements DataApi.DataListener, ConnectionCallbacks, OnConnectionFailedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Wearable.API) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } @Override protected void onStart() { super.onStart(); if (!mResolvingError) { mGoogleApiClient.connect(); } } @Override public void onConnected(Bundle connectionHint) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Connected to Google Api Service"); } Wearable.DataApi.addListener(mGoogleApiClient, this); } @Override protected void onStop() { if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) { Wearable.DataApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } super.onStop(); } @Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_DELETED) { Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri()); } else if (event.getType() == DataEvent.TYPE_CHANGED) { Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri()); } } }