NFC III – Ecrire sur un Tag sur Android

Le troisième billet de cette série sera l’occasion de détailler l’écriture d’un Tag NFC sur Android. Comme pour le billet précédent, mes propos seront illustrés grâce à l’application Zen Wifi NFC. Pour rappel, l’application Zen Wifi NFC permet de connecter automatiquement un appareil Android compatible NFC à un réseau Wifi, lors de la lecture d’un Tag spécifique. Les exemples de cet article concerneront donc l’écriture de notre Tag Wifi.

Tag Dispatch

À l’instar de la lecture, l’écriture d’un Tag nécessite au préalable la détection de celui-ci. Dans l’article précédent, j’ai détaillé le mécanisme d’Android de dispatch par Intent, des Tag détectés, ainsi que le foreground dispatch, permettant de court-circuiter le précédent.
Bien que ce ne soit pas une obligation, dans 99% des cas d’usage, on utilisera le foreground dispatch pour l’écriture d’un Tag.

Zen Wifi NFC

Le Tag doit, bien sûr, supporter la technologie Ndef, c’est ce qu’on indique dans notre filtre. Par contre, nous ne filtrons pas sur le type de données ici, car peu importe les données présentes sur le Tag, nous allons les réécrire.
De même, nous filtrons les Intent avec l’action TECH_DISCOVERED car il n’est pas nécessaire que le Tag contienne un message Ndef.

@Override
protected void onResume() {
	super.onResume();
	PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0,
			new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
	IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
	IntentFilter[] intentFiltersArray = new IntentFilter[] {ndef};
	String[][] techListsArray = new String[][] {new String[] {Ndef.class.getName()}};
	mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}

NB : Notre application filtre les Tag qui supportent la technologie Ndef, ce qui signifie que les Tag sont formatés au format Ndef, mais ne détectera pas les Tag qui peuvent être formatés au format Ndef (mais qui, donc, ne le sont pas encore). Ces derniers indiquent supporter la technologie NdefFormatable. Il est tout à fait possible de formater le Tag avec Android mais une anomalie affecte certaines version d’ICS. En effet, sur ICS 4.0.2 (qui équipe actuellement les Galaxy Nexus) les Tags non formatés n’indiquent plus être NdefFormatable. Ce bug est corrigé sur ICS 4.0.3 (qui équipe actuellement la plupart des Nexus S). Pour simplifier, ici, on n’écrit que sur des Tags déjà formatés Ndef.

Écriture du Tag

Puisqu’on utilise le foreground dispatch et qu’on a demandé à Android de ne pas redémarrer l’Activity (flag ACTIVITY_SINGLE_TOP), une fois le Tag détecté, c’est la méthode onNewIntent qui est appelée. l’Intent en paramètre de cette méthode possède un extra EXTRA_TAG qui permet d’obtenir un objet Tag.

@Override
protected void onNewIntent(Intent intent) {
	Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
	[...]
}

Pour pouvoir effectuer des I/O, nous devons obtenir une instance d’une implémentation de TagTechnology, qui sont les technologies qu’un Tag peut supporter (voir le package android.nfc.tech). Chaque classe d’implémentation possède une méthode statique get qui prend un Tag en paramètre et qui renvoie une instance de cette classe d’implémentation si, et seulement si, la technologie est supportée par le Tag.

Zen Wifi NFC

Pour notre Tag Wifi, nous souhaitons donc utiliser la technologie Ndef. Cette classe possède une méthode writeNdefMessage, qui prend en paramètre un NdefMessage. Avant de pouvoir écrire le message, nous devons nous y connecter grâce à la méthode connect. De même, après l’écriture, nous appelons la méthode close.
Ces 3 méthodes ne doivent pas être appelées depuis le Thread de l’UI.

@Override
protected void onNewIntent(Intent intent) {
	Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
	final Ndef ndef = Ndef.get(tag);
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				ndef.connect();
				try {
					ndef.writeNdefMessage(getMessage());
				}
				catch(FormatException e) {
					Log.e("NFC", e.getMessage(), e);
				}
				ndef.close();
			}
			catch(IOException e) {
				Log.e("NFC", e.getMessage(), e);
			}
		}
	}).start();
}

Message Ndef

Comme vu dans les articles précédents, un message Ndef est constitué de un ou plusieurs enregistrements. Sur Android, les messages Ndef sont représentés par la classe NdefMessage et les enregistrements par la classe NdefRecord, ainsi, naturellement, le constructeur de la classe NdefMessage utilise un tableau de NdefRecord.
Le constructeur de NdefRecord prend 4 paramètres : le TNF (Type Name Format), le type de la donnée, l’id de l’enregistrement et le payload. Les 3 derniers paramètres sont des tableaux de byte.
L’étude des différents TNF définis par le NFC Forum sera l’objet d’un article à part entière.

Zen Wifi NFC

Pour notre Tag Wifi, nous utiliserons le TNF EXTERNAL TYPE, car nous utilisons notre propre type. Nous construisons le NdefRecord ainsi:

private NdefMessage getMessage() throws FormatException {
	[...]
	NdefRecord record = new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE,
							"zenika.com:zenwifi".getBytes(), new byte[0], payload);
	NdefRecord[] records = {record};
	NdefMessage msg = new NdefMessage(records);
	return msg;
}

Pour la construction du payload, on récupère le ssid et la passkey depuis 2 EditText. On ajoute les doubles quotes si nécessaire, puis on concatène et on récupère les octets.

private NdefMessage getMessage() throws FormatException {
	StringBuilder payloadBuilder = new StringBuilder();
	String ssid = mSSID.getText().toString();
	if(TextUtils.isEmpty(ssid)) throw new FormatException("SSID is empty");
	if(!ssid.startsWith(DOUBLE_QUOTE)) payloadBuilder.append(DOUBLE_QUOTE);
	payloadBuilder.append(ssid);
	if(!ssid.endsWith(DOUBLE_QUOTE)) payloadBuilder.append(DOUBLE_QUOTE);
	if(payloadBuilder.length() <= 2) throw new FormatException("SSID is empty");
	int ssidSize = payloadBuilder.length();
	String pwd = mPSK.getText().toString();
	if(TextUtils.isEmpty(pwd)) throw new FormatException("Password is empty");
	if(!pwd.startsWith(DOUBLE_QUOTE)) payloadBuilder.append(DOUBLE_QUOTE);
	payloadBuilder.append(pwd);
	if(!pwd.endsWith(DOUBLE_QUOTE)) payloadBuilder.append(DOUBLE_QUOTE);
	if(payloadBuilder.length() <= ssidSize + 2) throw new FormatException("Password is empty");
	byte[] payload = payloadBuilder.toString().getBytes();
	[...]
	return msg;
}

Android Application Record

Nous pouvons désormais écrire sur un Tag nos identifiants de la box Wifi. L’application Zen Wifi NFC sera alors lancée dès qu’on scannera le Tag… à condition qu’elle soit installée sur l’appareil de l’utilisateur. En effet, si l’application n’est pas installée, rien ne se passera, ou alors une application générique qui lit tous les Tag sera lancée.
Il existe un moyen de contourner ce problème en ajoutant dans notre message un enregistrement qui indique précisément à Android quelle est l’application qui doit recevoir ce message. Android va alors tenter d’ouvrir ladite application, et si elle n’est pas installée, il lancera l’Android Market le Google Play Store pour que l’utilisateur installe l’application adéquate (à condition qu’elle soit publiée sur le Play Store).
Cet enregistrement spécifique s’appelle un AAR (pour Android Application Record), et s’obtient de la manière suivante:

NdefRecord aar = NdefRecord.createApplicationRecord(context.getPackageName());

Certains d’entre vous sont peut-être en train de se demander à quoi sert notre intent-filter mis en place dans l’article précédent dans le cas où on utilise un AAR ? Le détail important est qu’un AAR identifie une application (en utilisant son nom de package, qui est son identifiant) alors qu’un intent-filter se place sur un composant (une Activity, ici). En effet, avec seulement un AAR, Android va lancer l’application par son point d’entrée (filtre ACTION_MAIN / CATEGORY_LAUNCHER), ce n’est pas nécessairement le composant chargé de lire le Tag.

Conclusion

L’écriture sur un Tag sur Android se fait de manière similaire à la lecture, en 2 étapes : la gestion du dispatch et l’envoi du message. Le dispatch est, cependant, légèrement différend puisqu’on filtrera plus large (pas de restriction sur le type de la donnée contenue dans le tag le cas échéant), et on utilisera essentiellement le foreground dispatch.
Dans un prochain billet, nous étudierons les possibilités de faire du P2P avec Android, et notamment avec Beam.

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :