Utilisation du NFC sous Android pour établir une connexion Bluetooth

Sylvain Berfini

Cet article a pour but d'utiliser le capteur NFC présent sur certains AndroPhones afin de permettre à deux terminaux équipés d'établir une connexion Bluetooth sans faire intervenir l'utilisateur.

Commentez ♪

- Titre : Utilisation du NFC sous Android pour établir une connexion Bluetooth

- Auteur : Sylvain Berfini

- Parution : 28 mars 2011

- Licence : Copyright ® 2011 Sylvain Berfini. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Prérequis

- II. Préambule

- III. Squelette de l'application

- IV. Activity hôte

- V. Activity cliente

- VI. Remerciements

I - Prérequis

Afin que vous ne soyez pas perdu dans la lecture de ce tutoriel, il est indispensable qu'en plus des bases dans la programmation d'applications Android vous sachiez utiliser le capteur NFC pour envoyer et recevoir des données. Si ce n'est pas le cas, je vous invite à lire attentivement cet article : Introduction à l'utilisation du NFC dans une application Android.

La connaissance dans l'utilisation du Bluetooth en programmation Android n'est pas nécessaire en elle-même, cependant elle serait souhaitable. Vous pouvez si vous le souhaitez commencer par ce tutoriel : Introduction à l'utilisation du Bluetooth dans une application Android.

II - Préambule

Depuis la release 2.3.3 (API 10) du SDK d'Android, certaines nouvelles méthodes très pratiques ont fait leur apparition. Outre les améliorations de l'API pour ce qui concerne le NFC, il ne faut pas oublier la possibilité de créer/se connecter à un serveur (socket) Bluetooth non sécurisé.

Quel est l'avantage d'avoir un serveur non sécurisé alors qu'avant nous en avions un sécurisé ? Eh bien nous n'avons plus besoin de faire participer l'utilisateur pour nous connecter à un autre terminal en Bluetooth. Il suffit de connaître l'UUID de la socket, l'adresse MAC du périphérique Bluetooth du terminal et bien sûr d'être à portée. Et puisque ces deux informations s'avèrent n'être ni plus ni moins que des chaînes de caractères, il est très facile et peu coûteux (en taille) de les faire naviguer.

Et c'est là que le NFC entre en jeu. Puisque de toute façon il faut être proche (au sens "humain") pour instaurer un dialogue en Bluetooth, autant se servir du NFC dont l'inconvénient majeur (la portée) n'en est plus un. L'objectif de ce tutoriel va donc être de configurer et synchroniser une socket Bluetooth simplement en rapprochant deux terminaux équipés d'un capteur NFC.

III - Squelette de l'application

Après ce préambule, entrons maintenant dans les détails techniques. Je vais créer trois classes héritant d'Activity au sein de mon projet.

L'une (que j'appellerai "hôte") devra être lancée par un des deux terminaux qui créera une socket Bluetooth de type serveur et émettra un Tag (par Ndef Push) contenant l'UUID du serveur ainsi que son adresse MAC Bluetooth. Elle pourra être lancée depuis le menu par l'utilisateur voulant être l'hôte de la session.

La deuxième (dite "cliente") attendra la découverte d'un Tag pour se lancer, et tentera de se connecter au serveur dont il récupérera les informations dans le Tag reçu. Elle pourra également être lancée par l'utilisateur depuis le menu et lui permettra de tenter de se connecter à un couple adresse MAC/UUID connu lors d'une précédente session.

La dernière sera l'Activity principale, dite "menu", c'est-à-dire que lors d'une pression sur l'icône de mon application, ce sera elle qui sera lancée.

Mais avant de rentrer dans le code de celles-ci, voyons la partie "importante" du fichier Manifest.xml. Nous allons y déclarer nos trois Activity : le menu comme action Main/Launcher, le client comme action Tag_Discovered et l'hôte comme une Activity toute simple. Pour terminer, nous allons-y déclarer les permissions dont nous allons avoir besoin, à savoir celle pour gérer le capteur NFC (sans oublier le filtre pour interdire l'installation de notre application sur les terminaux ne possédant pas de capteur NFC), ainsi que celles pour administrer (comprendre allumer/éteindre) et utiliser le Bluetooth.

Manifest.xml

<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Menu" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Host" android:label="Host"/>
<activity android:name=".Client" android:label="Client">
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="10" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

Maintenant voyons le code de nos Activity. Je ne vais pas vous coller tout le contenu de chacune des trois classes (parce que cela ne vous apporterait rien sur le plan de l'apprentissage), mais juste les parties du code qui concernent l'interaction NFC/Bluetooth.

IV - Activity hôte

Si vous avez lu mes précédents tutoriels sur le NFC et sur le Bluetooth, rien ici ne devrait vous choquer. Je récupère l'adresse MAC de mon périphérique Bluetooth après avoir fait en sorte qu'il soit bien activé, puis je crée un NdefMessage qui contient cette adresse ainsi que l'UUID de mon serveur Bluetooth et je le push en utilisant le capteur NFC. Ensuite je crée la socket serveur Bluetooth et j'attends une connexion. La suite ne dépend plus que de vous...

Activity Hote

public class Hote extends Activity
{
private static final String BT_SERVER_UUID_INSECURE = "8ce255c0-200a-11e0-ac64-0800200c9a66"; // A vous d'en choisir un
private NfcAdapter nfcAdapter;
private BluetoothAdapter blueAdapter;
private NdefRecord blueMessage;
private BluetoothSocket blueSocket;

public void onCreate(Bundle savedInstanceState)
{
...
}

private NdefRecord createRecord(String msg)
{
byte[] langBytes = Locale.ENGLISH.getLanguage().getBytes(Charset.forName("US-ASCII"));
byte[] textBytes = msg.getBytes(Charset.forName("UTF-8"));
char status = (char) (langBytes.length);

byte[] data = new byte[1 + langBytes.length + textBytes.length];
data[0] = (byte) status;
System.arraycopy(langBytes, 0, data, 1, langBytes.length);
System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);

return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
}

protected void onPause()
{
super.onPause();
if(NfcAdapter.getDefaultAdapter(this) != null)
{
NfcAdapter.getDefaultAdapter(this).disableForegroundNdefPush(this);
}
}

protected void onResume()
{
super.onResume();

nfcAdapter = NfcAdapter.getDefaultAdapter(this);
blueAdapter = BluetoothAdapter.getDefaultAdapter();

if (nfcAdapter == null || blueAdapter == null)
{
finish();
return;
}
if (!blueAdapter.isEnabled())
blueAdapter.enable();

try
{
UUID blueUUID = UUID.fromString(BT_SERVER_UUID_INSECURE);
BluetoothServerSocket blueServerSocket = blueAdapter.listenUsingInsecureRfcommWithServiceRecord("MyBluetoothChatService", blueUUID);
String blueAddress = blueAdapter.getAddress();

blueMessage = createRecord(blueAddress + "$" + BT_SERVER_UUID_INSECURE);
NdefRecord[] rec = new NdefRecord[1];
rec[0] = record;
NdefMessage msg = new NdefMessage(rec);
nfcAdapter.enableForegroundNdefPush(this, msg);

// Je mets cet appel ici, mais il serait préférable de le faire dans un Thread différent (l'appel est bloquant)
blueSocket = blueServerSocket.accept();
...
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

V - Activity cliente

De la même façon, j'ai ici créé une Activity s'exécutant lors de la découverte d'un Tag et qui vérifie que le Bluetooth est bien activé (l'activant si nécessaire). Ensuite elle analyse le message reçu, en déduit l'adresse MAC et l'UUID du serveur Bluetooth et tente de s'y connecter.

Activity cliente

public class Client extends Activity
{
private NfcAdapter nfcAdapter;
private BluetoothAdapter blueAdapter;
private BluetoothSocket blueSocket;

public void onCreate(Bundle savedInstanceState)
{
...

nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null)
{
Toast.makeText(this, "Votre terminal n'est pas équipé d'un capteur NFC !", Toast.LENGTH_SHORT).show();
System.exit(0);
}

resolveIntent(getIntent());
}

private String getBluetoothAddress(String msg) // Renvoie l'adresse MAC du serveur
{
return msg.substring(0, 17);
}

private UUID getUUID(String msg) throws NumberFormatException // Renvoie l'UUID du serveur
{
return UUID.fromString(msg.substring(18)); // On commence à 18 pour sauter l'adresse MAC et le caractère de délimitation que j'ai utilisé dans la classe Hote
}

private String getText(final byte[] payload)
{
if (payload == null) return null;
try
{
String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
int languageCodeLength = payload[0] & 0077;
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}

void resolveIntent(Intent intent)
{
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action))
{
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage[] msgs;
if (rawMsgs != null)
{
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++)
{
msgs[i] = (NdefMessage) rawMsgs[i];
}

try
{
NdefRecord record = msgs[0].getRecords()[0];
String message = getText(record.getPayload());
String blueAddress = getBluetoothAddress(message);
UUID blueUUID = getUUID(message);

blueAdapter = BluetoothAdapter.getDefaultAdapter();
while (!blueAdapter.isEnabled())
blueAdapter.enable();
BluetoothDevice blueDevice = blueAdapter.getRemoteDevice(blueAddress);
blueSocket = blueDevice.createInsecureRfcommSocketToServiceRecord(blueUUID);
blueSocket.connect();

// Vous êtes connecté, à vous de faire ce que vous voulez
...
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
}

VI - Remerciements

Je voudrais remercier _Max_ et Claude Leloup pour leur relecture attentive.