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.
<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...
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.
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.