To send large blobs of binary data over the Bluetooth transport, such as images, attach an Asset
to a data item and the put the data item into the replicated data store.
Assets automatically handle caching of data to prevent retransmission and conserve Bluetooth bandwidth. A common pattern is for a handheld app to download an image, shrink it to an appropriate size for display on the wearable, and transmit it to the wearable app as an asset.
Note: Although the size of data items is limited to 100KB, assets can be as large as desired. However, transferring large assets affects the user experience in many cases, so test your apps to ensure that they perform well if you're transferring large assets. Create the asset using one of the create...()
methods in the Asset
class. Here, we convert a bitmap to a byte stream and then call createFromBytes()
to create the asset.
private static Asset createAssetFromBitmap(Bitmap bitmap) { final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream); return Asset.createFromBytes(byteStream.toByteArray()); }
When you have an asset, attach it to a data item with the putAsset()
method in DataMap
or PutDataRequest
and then put the data item into the data store with putDataItem()
:
Using PutDataRequest
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); Asset asset = createAssetFromBitmap(bitmap); PutDataRequest request = PutDataRequest.create("/image"); request.putAsset("profileImage", asset); Wearable.DataApi.putDataItem(mGoogleApiClient, request);
Using PutDataMapRequest
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); Asset asset = createAssetFromBitmap(bitmap); PutDataMapRequest dataMap = PutDataMapRequest.create("/image"); dataMap.getDataMap().putAsset("profileImage", asset) PutDataRequest request = dataMap.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi .putDataItem(mGoogleApiClient, request);
Here's an example of how to implement the callback to detect an asset change and extract the asset:
@Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/image")) { DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem()); Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage"); Bitmap bitmap = loadBitmapFromAsset(profileAsset); // Do something with the bitmap } } } public Bitmap loadBitmapFromAsset(Asset asset) { if (asset == null) { throw new IllegalArgumentException("Asset must be non-null"); } ConnectionResult result = mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS); if (!result.isSuccess()) { return null; } // convert asset into a file descriptor and block until it's ready InputStream assetInputStream = Wearable.DataApi.getFdForAsset( mGoogleApiClient, asset).await().getInputStream(); mGoogleApiClient.disconnect(); if (assetInputStream == null) { Log.w(TAG, "Requested an unknown Asset."); return null; } // decode the stream into a bitmap return BitmapFactory.decodeStream(assetInputStream); }> Sending and Receiving Messages
You send messages using the MessageApi
and attach the following items to the message:
- An arbitrary payload (optional)
- A path that uniquely identifies the message's action
Note: With versions of Google Play services prior to 7.3.0, only one wearable device could be connected to a handheld device at a time. You may need to update your existing code to take the multiple connected nodes feature into consideration. If you don’t implement the changes, your messages may not get delivered to intended devices.
Note: Capabilities are custom strings that you define and must be unique within your app.
Initially, you can detect the capable nodes by calling the CapabilityApi.getCapability()
method. The following example shows how to manually retrieve the results of reachable nodes with thevoice_transcription
capability:
private static final String VOICE_TRANSCRIPTION_CAPABILITY_NAME = "voice_transcription"; private GoogleApiClient mGoogleApiClient; ... private void setupVoiceTranscription() { CapabilityApi.GetCapabilityResult result = Wearable.CapabilityApi.getCapability( mGoogleApiClient, VOICE_TRANSCRIPTION_CAPABILITY_NAME, CapabilityApi.FILTER_REACHABLE).await(); updateTranscriptionCapability(result.getCapability()); }
To detect capable nodes as they connect to the wearable device, register aCapabilityApi.CapabilityListener()
instance to your GoogleApiClient
. The following example shows how to register the listener and retrieve the results of reachable nodes with the voice_transcription
capability:
private void setupVoiceTranscription() { ... CapabilityApi.CapabilityListener capabilityListener = new CapabilityApi.CapabilityListener() { @Override public void onCapabilityChanged(CapabilityInfo capabilityInfo) { updateTranscriptionCapability(capabilityInfo); } }; Wearable.CapabilityApi.addCapabilityListener( mGoogleApiClient, capabilityListener, VOICE_TRANSCRIPTION_CAPABILITY_NAME); }
Note: If you create a service that extends WearableListenerService
to detect capability changes, you may want to override the onConnectedNodes()
method to listen to finer-grained connectivity details, such as when a wearable device switches from Wi-Fi to a Bluetooth connection to the handset. For an example implementation, see the DisconnectListenerService
class in the FindMyPhone sample. For more information on how to listen for important events, see Listen for Data Layer Events.
To determine if a node is nearby, call the Node.isNearby()
method.
The following example shows how you might determine the best node to use:
private String transcriptionNodeId = null; private void updateTranscriptionCapability(CapabilityInfo capabilityInfo) { Set<Node> connectedNodes = capabilityInfo.getNodes(); transcriptionNodeId = pickBestNodeId(connectedNodes); } private String pickBestNodeId(Set<Node> nodes) { String bestNodeId = null; // Find a nearby node or pick one arbitrarily for (Node node : nodes) { if (node.isNearby()) { return node.getId(); } bestNodeId = node.getId(); } return bestNodeId; }
Note: A successful result code does not guarantee delivery of the message. If your app requires data reliability, use DataItem
objects or the ChannelApi
class to send data between devices.
public static final String VOICE_TRANSCRIPTION_MESSAGE_PATH = "/voice_transcription"; private void requestTranscription(byte[] voiceData) { if (transcriptionNodeId != null) { Wearable.MessageApi.sendMessage(googleApiClient, transcriptionNodeId, VOICE_TRANSCRIPTION_MESSAGE_PATH, voiceData).setResultCallback( new ResultCallback() { @Override public void onResult(SendMessageResult sendMessageResult) { if (!sendMessageResult.getStatus().isSuccess()) { // Failed to send message } } } ); } else { // Unable to retrieve node with transcription capability } }
Note: To learn more about asynchronous and synchronous calls to Google Play services and when to use each, see Communicate with Google Play Services.
You can also broadcast messages to all connected nodes. To retrieve all of the connected nodes that you can send messages to, implement the following code:
private Collection<String> getNodes() { HashSet <String>results = new HashSet<String>(); NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); for (Node node : nodes.getNodes()) { results.add(node.getId()); } return results; }> Handling Data Layer Events
When you make calls to the Data Layer API, 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.
You'll notice that calls to the Data Layer API sometimes return aPendingResult
, 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.
Asynchronous calls
If your code is running on the main UI thread, do not make blocking calls to the Data Layer API. You can run the calls asynchronously by adding a callback method 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()); } } });
Synchronous calls
If your code is running on a separate handler thread in a background service (which is the case in aWearableListenerService
), it's fine for the calls to block. In this case, you can call await()
on thePendingResult
object, which blocks until the request completes and returns a Result
object:
DataItemResult result = pendingResult.await(); if(result.getStatus().isSuccess()) { Log.d(TAG, "Data item set: " + result.getDataItem().getUri()); }
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 the data event callback methods for the events you are interested in handling.
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 the connection with the handheld or wearable is connected or disconnected. Changes in connection state on one side of the connection trigger these callbacks on both sides of the connection.
To create a WearableListenerService
:
- Create a class that extends
WearableListenerService
. - Listen for the events that you're interested in, 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 List events = 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); } } }
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 { private GoogleApiClient mGoogleApiClient; @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()); } } } }