Dans ce chapitre, on va s’intéresser à la connectivité de la montre et plus particulièrement à l’échange de données entre la montre et le téléphone mobile.
Connectivité de la montre
La montre communique avec internet au travers de la connexion bluetooth avec le téléphone. Ce dernier relaie donc les requêtes HTTP par exemple. En cas de perte de connexion, la montre bascule sur le wifi (si disponible) et communique en direct avec internet. Cela consomme plus d’énergie mais permet à la montre de maintenir des interactions avec le réseaux.

Pour ce qui concerne les interactions entre la téléphone et la montre, il existe plusieurs façons de les réaliser.
Tout d’abord, les notifications sont automatiquement envoyées à la montre, ce qui permet de ne pas avoir à programmer cet envoi dans l’application mobile et à développer une application correspondante pour la montre. Quand les données à envoyer sont plus complexes qu’une notification, et surtout lorsque l’on veut réaliser des traitements du côté de la montre et un affichage particulier, on doit utiliser des classes dédiées à la communication entre téléphone et montre.
Ces classes particulières permettent de répondre à trois types de besoins :
Envoi de message : Un message est construit par l’un des appareils et envoyé aux autres. Les messages utilisent la classe
MessageClient
.Synchronisation de données : un des deux appareils peut publier des données (par exemple une image) qui sont synchronisées sur tous les appareils. La synchronisation utilise les classes
DataItem
etAsset
.Envoi de gros fichiers: des données souvent volumineuses et que l’on ne veut pas synchroniser de manière persistante sont à envoyées. On utilise pour cela la classe
ChannelClient
.
L’envoi de données ou fichiers entre les appareils se fait au travers du canal de communication bluetooth, mais peut aussi se faire par les serveurs Google si la montre connectée se connecte au réseau au travers du wifi. On peut donc réaliser des communications entre plusieurs appareils distants au travers de ces APIs sans se soucier de la manière dont ils sont connectés entre eux, comme représenté sur le schéma ci-dessous et issu de la documentation fournie par Google.

Dans la suite, nous explorerons trois moyens de communication : les notifications, l'envoi de messages, et l'envoi de fichiers.
Notifications
Voyons maintenant le cas le plus simple qui permet de réaliser une interaction entre le téléphone et la montre : l’envoi de notifications.
Une notification, composée d’une icône, d’un titre et d’un texte, se crée classiquement de la manière suivante, du côté téléphone :
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this, "M_CH_ID")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("Hey !")
.setContentText("You should check this information...");
On ajoute ensuite un Intent
qui est “en attente” (PendingIntent
) puisqu’il s’agit d’un Intent
qui sera envoyé à l’application du téléphone si une action “My action” est réalisée sur la montre connectée. Du côté de la montre tout est automatique : la notification qui s’affiche possède alors un bouton “My action” et un clic sur celui-ci déclenche l'envoi de l'Intent
côté téléphone.
// Create an intent for the reply action getBroadcast enables to have a broadcast
// message sent to the phone
Intent actionIntent = new Intent("mynotification.intent");
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Create the action
NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_launcher_background,
"My action", actionPendingIntent)
.build();
// Add action
mBuilder.extend(new NotificationCompat.WearableExtender().addAction(action));
Sur le téléphone, pour réaliser un traitement associé à cet Intent
, il faut le filtrer et réaliser une action, par exemple ici, cocher la case à cocher :
// Waiting for the PendingIntent from the wear app
br_ = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("CIO", "PendingIntent received !");
CheckBox cb = findViewById(R.id.checkBox);
cb.setChecked(true); // Check the box !
}
};
IntentFilter filter = new IntentFilter("mynotification.intent");
this.registerReceiver(br_, filter);
À l’écran, on obtient l’image suivante, où la case à cocher est cochée à cause de l’appui sur “My action” :

On note qu’avec de tels mécanismes, on évite l’implémentation d’une application spécifique du côté de la montre tout en conservant la possibilité d’interagir avec l’utilisateur.
Messages
L’envoi de messages depuis la montre vers le téléphone (et inversement) va permettre de réaliser des traitements côté téléphone (et inversement). Depuis Android Wear 2, les objets à utiliser pour communiquer avec Wear ont changé et GoogleApiClient est déprécié . On peut se référer à la documentation pour voir quels sont les changements.
Dans la nouvelle API, un simple appel statique permet de récupérer un objet CapabilityClient
qui va servir à la communication :
CapabilityClient capabilityClient = Wearable.getCapabilityClient(this);
Puis, on peut interroger les capacités des noeuds connectés. Ainsi, on pourra filtrer ceux qui disposent d’une capacité qui nous intéresse. Les capacités sont simplement des chaînes de caractères que l’on déclare du côté du noeuds distants dans le fichier res/values/wear.xml
(ici, côté téléphone) :
<?xml version="1.0" encoding="utf-8"?>
name="android_wear_capabilities"
my_capability
Dans l’exemple ci-dessus, le téléphone déclare une capacité “my_capability”. Du côté montre, on peut alors chercher cette capacité dans une tâche asynchrone et filtrer celle qui nous intéresse. Dans l’exemple ci-dessous, côté montre, on affiche juste les capacités trouvées, et on enregistre tous les noeuds trouvés dans l’objet nodes (HashSet
) :
Task<Map<String, CapabilityInfo>> capabilitiesTask =
capabilityClient.getAllCapabilities(CapabilityClient.FILTER_REACHABLE);
capabilitiesTask.addOnSuccessListener(
new OnSuccessListener<Map<String, CapabilityInfo>>() {
@Override
public void onSuccess(Map<String, CapabilityInfo> capabilityInfoMap) {
nodes.clear();
if (capabilityInfoMap.isEmpty()) {
Log.i("CIO", "No capability advertised :/"); return;
}
for (String capabilityName : capabilityInfoMap.keySet()) {
CapabilityInfo capabilityInfo = capabilityInfoMap.get(capabilityName);
Log.i("CIO", "Capability found: " + capabilityInfo.getName());
if (capabilityInfo != null) {
nodes.addAll(capabilityInfo.getNodes());
}
}
Log.i("CIO", "Nodes found: " + nodes.toString());
TextView found = (TextView)findViewById(R.id.foundNodes);
StringBuilder printNodes = new StringBuilder();
for (Node node : nodes)
printNodes.append(node.getDisplayName()).append(" ");
found.setText("Nodes: " + printNodes);
}
});
Une fois les noeuds trouvés (le téléphone), la montre va envoyer un message en utilisant un objet MessageClient
. Sur celui-ci, on appelle la méthode sendMessage
avec l’id du noeud (téléphone) auquel on veut envoyer un message, et en second argument le message lui-même sous forme d’un tableau de byte.
public void clickSendMessage(View view) {
Log.i("CIO", "Sending a message...");
MessageClient clientMessage = Wearable.getMessageClient(this);
for (Node node : nodes) {
Log.i("CIO", " - to " + node.getId());
String message = "Hello " + counter;
Task<Integer> sendMessageTask =
clientMessage.sendMessage(node.getId(),"CIO", message.getBytes());
counter++;
}
Côté téléphone, le message est reçu si l'on place un écouteur dans l’activité :
Wearable.getMessageClient(this).addListener(this);
Ce qui oblige la classe de l’activité à implémenter l’interface OnMessageReceivedListener
:
public class MainActivity extends AppCompatActivity implements MessageClient.OnMessageReceivedListener
On implémente alors la méthode onMessageReceived
qui ici, récupère le message et l’ajoute à un TextView
:
@Override
public void onMessageReceived(@NonNull MessageEvent messageEvent) {
Log.i("CIO", "Received message: ");
Log.i("CIO", " - Path: " + messageEvent.getPath());
String message = new String(messageEvent.getData());
Log.i("CIO", " - Content: " + message);
TextView tv = (TextView)findViewById(R.id.textView);
tv.setText(tv.getText() + "\n" + message);
}
Le résultat d’une telle application est montré ci-dessous : le noeud détecté depuis la montre est un Nexus 5X et trois messages sont envoyés et affichés côté téléphone.

Synchronisation d’images
Pour finir, voyons comment il est possible de synchroniser une image du téléphone vers la montre.
De manière similaire à la précédente, on demande un client de communication de type DataClient
:
DataClient dataClient = Wearable.getDataClient(this);
Pour synchroniser l’image bitmap, il faut créer un objet Asset, et une map de correspondance qui va associer des clefs et des valeurs :
À “my_image” on associe l’asset (l’image) ;
À “time” on associe la date courante.
L’utilisation de la date courante permet de forcer l’envoi de la requête, même si l’image n’a pas changé. Cette map dataMap
est ensuite mise dans un objet PutDataRequest
qui est ensuite envoyé via l’objet dataClient
et la méthode putDataItem
:
Asset asset = toAsset(bitmap);
PutDataMapRequest dataMap = PutDataMapRequest.create("/Image");
dataMap.getDataMap().putAsset("my_image", asset);
dataMap.getDataMap().putLong("time", new Date().getTime());
PutDataRequest request = dataMap.asPutDataRequest();
request.setUrgent();
Log.i("CIO", "Preparing task...");
Task<DataItem> dataItemTask = dataClient.putDataItem(request);
dataItemTask.addOnSuccessListener(new OnSuccessListener<DataItem>() {
@Override
public void onSuccess(DataItem dataItem) {
Log.i("CIO", "Sent image: " + dataItem);
}
});
Du côté de la montre, il faut réceptionner l’asset et mettre à jour l’image sur l’écran. Pour se faire, on enregistre un écouteur de type DataClient
sur l’activité :
Wearable.getDataClient(this).addListener(this);
Cela oblige à implémenter l’interface OnDataChangedListener
:
public class MainActivity extends WearableActivity implements DataClient.OnDataChangedListener
On implémente alors la méthode onDataChanged
qui récupère l’asset.
public void onDataChanged(@NonNull DataEventBuffer dataEvents) {
Log.i("CIO", "Data changed !");
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
Log.i("CIO", " - Path: " + path);
Map<String, DataItemAsset> assets = event.getDataItem().getAssets();
DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
Asset asset = dataMapItem.getDataMap()
.getAsset("my_image");
new LoadBitmapAsyncTask().execute(asset);
Il faut encore implémenter une tâche asynchrone pour décoder le BitMap
depuis l’asset et mettre à jour l’interface graphique. Cette tâche est effectuée dans la classe LoadBitmapAsyncTask
.
private class LoadBitmapAsyncTask extends AsyncTask<Asset, Void, Bitmap> {
protected Bitmap doInBackground(Asset... params) {
if (params.length > 0) {
Asset asset = params[0];
Task<DataClient.GetFdForAssetResponse> getFdForAssetResponseTask =
Wearable.getDataClient(getApplicationContext()).getFdForAsset(asset);
try {
DataClient.GetFdForAssetResponse getFdForAssetResponse =
Tasks.await(getFdForAssetResponseTask);
InputStream assetInputStream = getFdForAssetResponse.getInputStream();
if (assetInputStream != null) {
return BitmapFactory.decodeStream(assetInputStream);
} else {
Log.w("CIO", "Requested an unknown Asset.");
return null;
}
} catch ...
} else {
Log.e("CIO", "Asset must be non-null");
return null;
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
Log.i("CIO", " - Setting background image on second page..");
ImageView img = (ImageView)findViewById(R.id.imageView);
img.setImageBitmap(bitmap);
}
}
}
On obtient alors la même image du côté de la montre et on peut l’afficher :
