Blog Zenika

#CodeTheWorld

IoT & Mobilité

NFC II – Lecture d'un Tag sur Android

Dans cet article, nous allons voir comment lire un Tag NFC sur Android. L’article sera illustré d’exemples issus de l’application Zen Wifi NFC. 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.

Tag Dispatch

La puce NFC des appareils Android n’est active que si l’utilisateur en décide ainsi dans ses paramètres et si l’écran de l’appareil est déverrouillé. Lorsqu’un Tag est détecté, Android va tenter de découvrir le type MIME de la donnée et aussi son URI. Ces informations, ainsi que le Tag, sont ensuite enveloppés dans un Intent.
En fonction du type de message contenu dans le Tag, l’action de l’Intent envoyé sera différente.
Ainsi, si le Tag contient un message NDEF correctement formatté, l’action sera NDEF_DISCOVERED.
Si le Tag ne contient pas de message NDEF, ou si à partir de celui-ci aucun type MIME ou URI ne peut être identifié, ou si aucune application ne possède de filtre pour l’action NDEF_DISCOVERED, alors l’action de l’Intent envoyé sera TECH_DISCOVERED.
Enfin, si aucune Activity ne possède le filtre adéquat, alors l’Intent est envoyé avec l’action TAG_DISCOVERED.
Le schéma ci dessous résume le mécanisme de dispatch.
Tag Dispatch System

Configurer son application

Plusieurs points de configuration sont nécessaires au niveau du fichier AndroidManifest.xml.

Demander la permission

Pour pouvoir utiliser le NFC, votre application doit demander la permission.

<uses-permission android:name="android.permission.NFC" />

Préciser la version minimum du SDK

Il est théoriquement possible de mettre 9 (Gingerbread) pour la propriété minSdkVersion, mais l’Api NFC de cette version est plus que limitée. Je conseille donc de mettre 10 (Gingerbread MR1), voire 14 pour l’utilisation de Beam (Beam sera abordé dans un futur billet).

<uses-sdk android:minSdkVersion="10"/>

Cibler uniquement les appareils compatibles

Le Tag uses-feature permet de n’afficher l’application dans le market que pour les appareils qui possèdent cette fonctionnalité. Cependant, si le NFC n’est pas une fonctionnalité essentielle de votre application, il est possible de cibler plus large et de vérifier la disponibilité du NFC au runtime (ceci est également vrai concernant le minSdkVersion).

<uses-feature android:name="android.hardware.nfc" android:required="true" />

Utiliser le bon filtre

Il est important que le ou les intent-filter placés sur vos Activity soit paramétrés avec la plus grande attention. Votre application ne doit pas être proposée à l’utilisateur si elle n’est pas intéressée par le Tag qui vient d’être découvert (e.g. le Tag contient du texte brut, et votre application ne s’intéresse qu’aux Tag contenant une Url).
De même, il est important de filtrer sur la bonne action. En filtrant sur l’action TAG_DISCOVERED, votre application ne sera appelée que si aucune autre n’est en mesure de gérer le Tag, ce qui est peu probable.

Zen Wifi NFC

L’application Zen Wifi NFC utilise un type de données spécifique, ce n’est pas un type standard.
La norme du NFC Forum permet de définir ses propres types en utilisant le TNF (Type Name Format) External Type.
Le type de notre donnée sera zenwifi. L’Urn de ce type, tel que défini par le NFC Forum sera
urn:nfc:ext:zenika.com:zenwifi.
A la lecture d’un Tag de tel type, Android transforme l’Urn en l’Uri suivante
vnd.android.nfc://ext/zenika.com:zenwifi.
Nous utiliserons par conséquent le filtre suivant.

<activity android:name=".WifiReaderActivity">
	<intent-filter>
		<action android:name="android.nfc.action.NDEF_DISCOVERED" />
		<category android:name="android.intent.category.DEFAULT" />
		<data android:scheme="vnd.android.nfc"
        		android:host="ext"
        		android:path="/zenika.com:zenwifi"/>
	</intent-filter>
</activity>

Foreground Dispatch

Une Activity au premier plan peut court-circuiter le dispatch par Intent grâce au foreground dispatch. La mise en place se fait dans l’Activity. On commence par obtenir une instance du NfcAdapter.

public class ForegroundDispatchTest extends Activity {
	private NfcAdapter	mNfcAdapter;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
		if(mNfcAdapter == null) {
			// NFC is not available
			finish();
			return;
		}
		if(!mNfcAdapter.isEnabled()) {
			// NFC is disabled
			finish();
			return;
		}
	}
}

Il est obligatoire que l’Activity soit au premier plan, on active donc le foreground dispatch dans onResume et on le désactive dans le onPause.

public class ForegroundDispatchTest extends Activity {
	private NfcAdapter	mNfcAdapter;
	[...]
	@Override
	protected void onResume() {
		super.onResume();
		[...]
		mNfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, techListsArray);
	}
	@Override
	protected void onPause() {
		mNfcAdapter.disableForegroundDispatch(this);
		super.onPause();
	}
}

La méthode enableForegroundDispatch prend en paramètre l’Activity cible du dispatch, un PendingIntent, un tableau d’IntentFilter et un tableau à double entrée de String, qui représente les technologies qui doivent être supportées
par le Tag.
Pour qu’un Tag soit dispatché, il faut qu’il supporte toutes les technologies indiquées dans une des entrées de premier niveau du dernier paramètre (ET logique entre les éléments de second niveau, OU logique entre les éléments de premier niveau).
Il est possible de passer null pour les 2 derniers paramètres ; tous les Tag seront alors reçus.

Zen Wifi NFC

On utilisera la même Activity pour déclencher le foreground dispatch et pour analyser les Tag captés. On utilise donc getClass pour préciser la classe de l’Intent, mais pour ne pas superposer plusieurs fois le même écran, on ajoute un flag qui permet de ne pas redémarrer l’Activity si elle est déjà au premier plan.
On utilise le même filtre que dans le manifest.
Au niveau des technologies, on demande à ce que le Tag supporte les messages NDEF.
A titre personnel, je trouve le dernier paramètre redondant lorsqu’on filtre l’action NDEF_DISCOVERED, mais il est obligatoire.

@Override
protected void onResume() {
	super.onResume();
	PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
			new Intent(mContext, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
	IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
	ndefFilter.addDataScheme("vnd.android.nfc");
	ndefFilter.addDataAuthority("ext", null);
	ndefFilter.addDataPath("/zenika.com:zenwifi", PatternMatcher.PATTERN_LITERAL);
	IntentFilter[] filters = {ndefFilter};
	String[][] techs = {{Ndef.class.getName()}};
	mNfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, techs);
}

Lire le contenu du Tag

Si notre application est active, et le foreground dispatch effectif, puisque l’Activity ne sera pas redémarrée, c’est la méthode onNewIntent qui sera appelée. Dans les autres cas, l’Intent sera passé à notre Activity via onCreate.

@Override
protected void onNewIntent(Intent intent) {
	if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
		[...]
	}
}

L’Intent reçu contient obligatoirement le Tag découvert dans l’extra EXTRA_TAG

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

De plus, si l’action de l’Intent est NDEF_DISCOVERED, on peut récupérer directement les messages NDEF à partir de l’extra EXTRA_NDEF_MESSAGES.
Pour obtenir le premier enregistrement du premier message, on procède de la manière suivante.

Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if(rawMsgs != null && rawMsgs.length > 0) {
	NdefMessage message = (NdefMessage) rawMsgs[0];
	NdefRecord[] records = message.getRecords();
	if(records.length > 0) {
		NdefRecord record = records[0];
		[...]
	}
}

Déchiffrage du payload

On obtient le payload (contenu de notre enregistrement) sous forme de tableau de byte de la manière suivante.

byte[] payload = record.getPayload();

Zen Wifi NFC

Le payload est la concaténation du SSID du réseau Wifi et de sa clé d’authentification, tous deux sous forme de chaînes de caractères entourées de double quote.

"example_SSID""password"

Les doubles quotes sont nécessaires pour l’Api de configuration du Wifi sur Android. Voici comment on procède.

String ssidPwd = new String(record.getPayload());
int idx = ssidPwd.indexOf("\"\"");
if(idx > 0) {
	String ssid = ssidPwd.substring(0, idx + 1);
	String pwd = ssidPwd.substring(idx + 1);
	WifiConfiguration conf = new WifiConfiguration();
	conf.SSID = ssid;
	conf.preSharedKey = pwd;
	WifiManager wManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
	int id = wManager.addNetwork(conf);
	if(wManager.enableNetwork(id, false)) {
		finish();
	}
	else {
		//...
	}
}

Conclusion

Pour le développeur, la lecture d’un Tag NFC se fait en 2 étapes : la gestion du dispatch et la récupération du contenu.
Le mécanisme de dispatch avec Intent permet d’utiliser la puissance des IntentFilter et de ne recevoir que les Tag qui nous intéressent.
Ensuite, le contenu du Tag dépend du besoin. Quelques types de données sont spécifiés par le NFC Forum, et le format du payload doit être conforme à la spécification, mais il est également possible d’utiliser ses propres formats.
Dans le prochain article, nous verrons comment écrire des données sur un Tag.

Une réflexion sur “NFC II – Lecture d'un Tag sur Android

  • Juste une petite question, tu utilises quel logiciel pour réaliser tes schémas stp ?

    Répondre

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.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading