Urban Airship.
Pour pouvoir s'enregistrer aux flux de notifications, les téléphones devront également être identifiés par un compte Google et avoir l'application Android Market d'installée. Si ces conditions sont immédiatement vérifiées sur un téléphone, il s'avère plus délicat de les réunir sur l'émulateur Android fourni avec le SDK qui ne prévoit pas d'utiliser de compte Gmail. Le tutoriel suivant vous permettra d'effectuer les modifications et ajouts d'apk nécessaires (disponibles ici) pour pouvoir tester les notifications sur votre émulateur. Cependant, l'utilisation d'un téléphone à jour reste le moyen le plus simple et rapide de tester l'application !
Notre application doit s'enregistrer auprès des serveurs C2DM pour que ceux-ci puissent lui envoyer les messages reçus de votre serveur d'application tiers. Pour cela, deux informations doivent être envoyées aux serveurs C2DM :
- le sender id : c'est l'adresse gmail dédiée à notre application qui va être utilisée par votre serveur d'application pour l'authentifier aux services google. Dans notre exemple, ça sera "notif.application@gmail.com
".
- l'application id : c'est l'identifiant qui permettra aux serveurs C2DM de n'envoyer les messages qu'à notre application Android. Cet application id doit contenir le nom de package (pour notre exemple : "com.octo.notif
") qui est l'identifiant unique de notre application sur Android.
A partir de ces informations, les serveurs Google vont générer un jeton, le registration id et le renvoyer à l'application.
Ce jeton nous permettra d'envoyer un message à partir du serveur tiers. Une fois reçu, il doit donc être transmis à notre serveur d'application pour être stocké.
Il faut savoir que les serveurs de Google regénèrent régulièrement les registration id. Notre application devra donc être capable de reconnaitre ultérieurement la réception d'un nouveau jeton et l'envoyer alors au serveur d'application pour remplacer l'ancien.
Une fois cette étape d'abonnement terminée, l'application Android est prête à recevoir des message en push. Voyons comment implémenter ces envois et réceptions d'informations.
Techniquement, votre application va avoir besoin de déclarer certaines permissions dans son fichier manifest pour envoyer des informations aux serveurs C2DM et recevoir le résultat de l'abonnement ainsi que des notifications :
- l'accès à internet :
<uses-permission android:name="android.permission.INTERNET"/>
- le droit de recevoir des messages (abonnement ou notifications) des serveurs C2DM :
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Nous allons sécuriser cette réception de messages C2DM pour qu'ils ne soient reçus que par notre seule application "com.octo.notif
" :
<permission android:name="com.octo.notif.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.octo.notif.permission.C2D_MESSAGE" />
Ensuite, pour nous abonner, il suffit de lancer un service d'abonnement identifié par l'intent com.google.android.c2dm.intent.REGISTER
en lui spécifiant le sender id et l'application id. Cette dernière information sera retrouvée par un PendingIntent :
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
registrationIntent.putExtra("sender", "notif.application@gmail.com");
context.startService(registrationIntent);
De la même manière, la procédure de désabonnement consiste à démarrer le service com.google.android.c2dm.intent.UNREGISTER
:
Intent unregistrationIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregistrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
context.startService(unregistrationIntent);
La réponse à l'abonnement ou au désabonnement ainsi que les messages sont envoyés à l'application en tant qu'événements broadcast. Nous allons donc créer la classe C2DMBroadcastReceiver.java
implémentant BroadcastReceiver, qui vérifiera quel type de message a été reçu et appellera selon le cas une des méthodes onError(), onRegistration(), onUnregistration()
ou onMessageReceived()
. Nous pourrons alors créer une classe héritant de C2DMBroadcastReceiver.java
qui externalisera ces différents traitements.
public abstract class C2DMBroadcastReceiver extends BroadcastReceiver {
protected abstract void onError(Context context, String error);
protected abstract void onRegistration(Context context, String registrationId);
protected abstract void onUnregistration(Context context);
protected abstract void onMessageReceived(Context context, Intent intent);
@Override
// méthode appelée lors de l'événement broadcast (les infos sont contenues dans l'intent)
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
handleRegistration(context, intent);
} else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
onMessageReceived(context, intent);
}
}
private void handleRegistration(Context context, Intent intent) {
String error = intent.getStringExtra("error");
String unregistration = intent.getStringExtra("unregistered");
String registration = intent.getStringExtra("registration_id");
if (error != null) {
onError(context, error);
} else if (unregistration != null) {
onUnregistration(context);
} else if (registration != null) {
onRegistration(context, registration);
}
}
}
Créons ensuite la classe de traitement NotificationsReceiver.java
où nous pourrons gérer les résultats comme nous le souhaitons (affichage d'un message d'erreur, nouvel essai d'abonnement, envoi par webservice du registration id, sauvegarde de celui-ci...) :
public class NotificationsReceiver extends C2DMBroadcastReceiver {
@Override
protected void onError(Context context, String error) {
// traitement de l'erreur
}
@Override
protected void onRegistration(Context context, String registrationId) {
// envoi du registrationId au serveur d'application
}
@Override
protected void onUnregistration(Context context) {
// traitement du désabonnement
}
@Override
protected void onMessageReceived(Context context, Intent intent) {
// traitement du message reçu
}
}
Pour que cette classe soit utilisée lorsqu'un événement broadcast est détecté, il ne reste qu'à mettre à jour le fichier manifest. Nous rajoutons au même niveau que les activités un receiver capable de traiter les messages C2DM uniquement :
<receiver android:name=".NotificationsReceiver" android:permission="com.google.android.c2dm.permission.SEND">
<!-- Recevoir le registration id -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION"></action>
<category android:name="com.octo.notif"></category>
</intent-filter>
<!-- Recevoir un message -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"></action>
<category android:name="com.octo.notif"></category>
</intent-filter>
</receiver>
Voilà ! Notre application est prête à s'abonner et recevoir des notifications ! Google propose une implémentation plus complète de ce système dans l'exemple ChromeToPhone qui vous permettra de voir comment retenter un abonnement après une erreur, comment garder le registration id en mémoire, etc.
Facebook utilise le push par exemple pour nous signaler qu'un nouveau message est disponible ou qu'une personne souhaite devenir notre ami... L'application redirige alors l'utilisateur vers la page permettant d'afficher l'information dans son intégralité. Implémentons un comportement similaire : lorsque notre application reçoit une notification push, nous souhaitons que celle-ci apparaisse dans la barre de notification du téléphone et que lorsque nous cliquons sur son résumé, nous arrivions par exemple sur la première page de notre application.
Cette action se définit dans notre méthode onMessageReceived()
:
@Override
protected void onMessageReceived(Context context, Intent intent) {
String message = intent.getStringExtra("message"); // data.message contient le texte de la notification
String title = "OctoWorldCup notif";
int iconId = R.drawable.notif_image;
// création de la notification :
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(iconId, message, System.currentTimeMillis());
// création de l'activité à démarrer lors du clic :
Intent notifIntent = new Intent(context.getApplicationContext(), MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notifIntent, 0);
// affichage de la notification dans le menu déroulant :
notification.setLatestEventInfo(context, title, message, contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL; // la notification disparaitra une fois cliquée
// lancement de la notification :
notificationManager.notify(1, notification);
}
Pour aller plus loin dans la personnalisation de la barre de notification, vous pouvez vous baser sur la documentation ici.
Il ne reste plus qu'à tester l'envoi d'une notification. Cela consiste côté serveur d'application à s'authentifier auprès des services Google https://www.google.com/accounts/ClientLogin avec le compte dédié ("notif.application@gmail.com"). Une fois identifié, pour chaque registration id, nous pouvons envoyer notre message par une requête POST à l'url https://android.apis.google.com/c2dm/send :
La façon d'implémenter l'envoi de message dépend de votre serveur et des technologies que vous souhaitez utiliser. Ce tutoriel vous propose une implémentation en Python.
Afin de tester rapidement notre fonctionnement, voici un script shell qui vous permettra d'envoyer simplement un message vers un mobile identifié par son registration id :
#!/bin/sh
# to use : ./authentAndSendMessage google_account_mail google_account_password registration_id "message"
# parameters
email=$1
password=$2
registration_id=$3
message=$4
# authentication
echo '--> Authenticate to google services'
authentication_result=`curl -s https://www.google.com/accounts/ClientLogin \
-d Email=$email \
-d "Passwd=$password" \
-d accountType=GOOGLE \
-d service=ac2dm`
# if authentication ok
if [[ $authentication_result == *Auth=* ]]
then
echo 'Authentication successful'
authentication=`echo $authentication_result | awk '{split($0,array,"Auth="); print array[2]}'`
echo 'Authentication token extracted'
echo '--> Sending message "'$message'"'
# send message
curl --header "Authorization: GoogleLogin auth=$authentication" "https://android.apis.google.com/c2dm/send" \
-d registration_id=$registration_id \
-d "data.message=$message" \
-d collapse_key=1
echo 'finished'
fi
Dans ce script, on peut voir dans la requête d'envoi POST que le texte de la notification est passé en paramètre dans la variable data.message
. Nous aurions pu ajouter d'autres informations avec d'autres clés data.variable
pour transmettre plus d'informations.
Dans cette même requête, une autre propriété est intéressante : collapse_key
. Cette clé est un identifiant que l'on donne aux messages de nature similaire. Ainsi, si le téléphone est éteint et que plusieurs messages avec le même identifiant s'accumulent sur le serveur C2DM, seul le dernier sera envoyé lorsque l'appareil aura été rallumé. Il convient donc de choisir avec soin ces identifiants.
Si les services C2DM ne permettent aujourd'hui que de toucher 60 % des smartphones du parc mondial Android, l'arrivée massive de tablettes en version 3, ainsi que la possibilité de mettre à jour son téléphone en 2.2 offerte par la quasi totalité des constructeurs/opérateurs laisse présager que plus de 80 % des appareils seront compatibles avant la fin de l'année 2011. Soyez donc prêts !