I. Introduction

Pour bien commencer, assurons-nous d'avoir le droit de l'utiliser et que le terminal qui exécute notre application possède un périphérique Bluetooth.

Demande de la permission d'utiliser le Bluetooth
Sélectionnez
<uses-permission android:name="android.permission.BLUETOOTH" />
Vérification de la présence du Bluetooth sur le terminal
Sélectionnez
BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter();
if (blueAdapter == null) {
    // Le terminal ne possède pas le Bluetooth
}

Avant d'être en mesure d'utiliser le Bluetooth dans votre application, il peut être de bonne pratique de vérifier si le Bluetooth est activé.

II. Activer le Bluetooth

Comme il est fort probable que le Bluetooth ne soit pas activé au moment voulu (beaucoup d'utilisateurs le désactivent pour éviter de recevoir des notifications de connexion ou tout simplement pour économiser leur batterie), voyons les deux manières possibles de faire pour l'allumer.

La première façon est de demander à l'utilisateur de le faire lui-même. Pour cela nous allons afficher une boîte de dialogue demandant à l'utilisateur s'il veut oui ou non activer son Bluetooth. Je vous rassure, pas la peine de coder vous-même la boîte de dialogue, le système sait le faire tout seul. Elle contiendra le message dans la langue du système de l'utilisateur ainsi que deux boutons, "Oui" et "Non".

Boîte de dialogue de confirmation d'activation du Bluetooth
Demande d'activation du Bluetooth
Sélectionnez
if (!blueAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

En utilisant "onActivityResult()", vous serez capable de savoir si l'utilisateur a cliqué sur oui ou non. Si le Bluetooth s'est correctement allumé, vous aurez un "RESULT_OK". Dans le cas contraire le retour sera "RESULT_CANCELED".

Vous pouvez également écouter l'Intent de broadcast "ACTION_STATE_CHANGED" pour savoir quand l'état du Bluetooth change. Vous pourrez alors utiliser "EXTRA_STATE" et "EXTRA_PREVIOUS_STATE" pour connaître l'état courant et l'état antérieur du Bluetooth. Les valeurs possibles sont "STATE_TURNING_ON", "STATE_ON", "STATE_TURNING_OFF" et "STATE_OFF".

La deuxième façon de faire est d'activer le Bluetooth directement, sans demander son consentement à l'utilisateur. Pour ce faire il va nous falloir demander une nouvelle permission.

Permission pour allumer et éteindre le Bluetooth
Sélectionnez
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
Allumage du Bluetooth
Sélectionnez
if (!blueAdapter.isEnabled()) {
    blueAdapter.enable();

Activer la visibilité de votre terminal par Bluetooth active automatiquement le Bluetooth s'il n'est pas déjà actif (cf. plus loin).

III. Découvrir les périphériques

De même, activer la découverte des périphériques proches peut réduire de manière significative le débit d'une connexion Bluetooth préalablement établie.

Avant de chercher les périphériques à proximité pour une éventuelle connexion, commençons par regarder parmi les périphériques que nous connaissons. Pour cela nous allons appeler une fonction nommée "getBondedDevices()" qui renvoie une liste de "BluetoothDevice", chacun étant un périphérique connu.

Récupération des périphériques connus
Sélectionnez
Set<BluetoothDevice> pairedDevices = blueAdapter.getBondedDevices();

Si le périphérique ne fait pas partie de ceux qui sont connus, il va falloir rechercher les terminaux visibles proches. Pour cela nous allons appeler "startDiscovery()", "cancelDiscovery()" permettant de stopper cette recherche. En général le processus dure une grosse dizaine de secondes, suivi d'une page qui affiche les périphériques découverts. Voyons comment faire pour récupérer les résultats.

Découverte des terminaux Bluetooth visibles et proches
Sélectionnez
// On crée un BroadcastReceiver pour ACTION_FOUND
private final BroadcastReceiver receiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // Quand la recherche trouve un terminal
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // On récupère l'object BluetoothDevice depuis l'Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // On ajoute le nom et l'adresse du périphérique dans un ArrayAdapter (par exemple pour l'afficher dans une ListView)
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Inscrire le BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter); // N'oubliez pas de le désinscrire lors du OnDestroy() !

La découverte des périphériques Bluetooth à proximité est très coûteuse en énergie. Pensez donc à appeler "cancelDiscovery" dès que possible.

Si les deux terminaux qui vont se connecter n'ont jamais été interconnectés, le système va automatiquement afficher une boîte de dialogue de confirmation. De ce fait vous n'avez pas à vous préoccuper au niveau de votre application de savoir si les terminaux se connaissent ou pas. La connexion va attendre que l'utilisateur valide la demande ou elle va échouer en cas de refus ou de timeout.

Demande de confirmation de synchronisation

Par défaut, même si le Bluetooth est activé, les terminaux Android ne sont pas visibles. Pour activer la visibilité Bluetooth, nous allons procéder de la même manière que pour activer le Bluetooth. Nous allons demander à l'utilisateur de le faire par le biais d'une boîte de dialogue. Notez que par défaut la durée de visibilité est de 120 secondes, mais vous pouvez définir une durée de votre choix, inférieure ou égale à 300 secondes.

Demande à l'utilisateur l'autorisation pour rendre visible le terminal
Demande à l'utilisateur l'autorisation pour rendre visible le terminal
Sélectionnez
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeVisible); // Cette ligne permet de définir une durée de visibilité de notre choix
startActivityForResult(discoverableIntent, REQUEST_DISCOVERABLE_BT);

Une fois encore vous pourrez récupérer le choix de l'utilisateur dans le "onActivityResult()", de la même manière que précédemment. Un "RESULT_OK" vous assurera de la bonne visibilité de votre terminal.

Vous pouvez également être informé du changement de visibilité de votre terminal en inscrivant un "BroadcastReceiver" pour l'Intent "ACTION_SCAN_MODE_CHANGED". Les champs "EXTRA_SCAN_MODE" et "EXTRA_PREVIOUS_SCAN_MODE" vous donneront les informations sur l'état précédent et actuel de la visibilité du périphérique. Les valeurs possibles sont "SCAN_MODE_CONNECTABLE_DISCOVERABLE", "SCAN_MODE_CONNECTABLE", ou "SCAN_MODE_NONE".

Il n'est pas nécessaire pour un terminal d'être visible pour établir une connexion vers un autre terminal. Seul celui qui va héberger la connexion doit être visible pour que le client puisse le découvrir et initier la connexion.

IV. Etablir une connexion côté serveur

La procédure à suivre est la suivante. Nous allons récupérer une "BluetoothServerSocket" en appelant "listenUsingRfcommWithServiceRecord(String, UUID)". Puis nous allons attendre une connexion par le biais de la méthode "accept()" de la socket. Une fois le(s) client(s) connecté(s), on bloque les demandes arrivant avec un appel à "close()".

Essayez de faire votre appel à la méthode "accept()" dans un Thread séparé car la méthode est bloquante. Cela aura pour effet de provoquer un ANR (Activity Not Responding) si le client met trop de temps à se connecter. Notez que toutes les méthodes des classes "BluetoothServerSocket" et "BluetoothSocket" sont "thread-safe".

Voici un thread simple qui accepte les connexions Bluetooth entrantes.

Exemple de thread Bluetooth serveur
Sélectionnez
private class ServeurBluetooth extends Thread {
    private final BluetoothServerSocket blueServerSocket;

    public ServeurBluetooth() {
        // On utilise un objet temporaire qui sera assigné plus tard à blueServerSocket car blueServerSocket est "final"
        BluetoothServerSocket tmp = null;
        try {
            // MON_UUID est l'UUID (comprenez identifiant serveur) de l'application. Cette valeur est nécessaire côté client également !
            tmp = blueAdapter.listenUsingRfcommWithServiceRecord(NOM, MON_UUID);
        } catch (IOException e) { }
        blueServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket blueSocket = null;
        // On attend une erreur ou une connexion entrante
        while (true) {
            try {
                blueSocket = blueServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // Si une connexion est acceptée
            if (blueSocket != null) {
                // On fait ce qu'on veut de la connexion (dans un thread séparé), à vous de la créer
                manageConnectedSocket(blueSocket);
                blueServerSocket.close();
                break;
            }
        }
    }

    // On stoppe l'écoute des connexions et on tue le thread
    public void cancel() {
        try {
            blueServerSocket.close();
        } catch (IOException e) { }
    }
}

V. Etablir une connexion côté client

Voyons maintenant comment connecter un client à notre serveur. Nous allons supposer que nous avons déjà l'objet "BluetoothDevice" du terminal serveur (voir ci-avant).

Exemple de thread Bluetooth serveur
Sélectionnez
private class ClientBluetooth extends Thread {
    private final BluetoothSocket blueSocket;
    private final BluetoothDevice blueDevice;

    public ClientBluetooth(BluetoothDevice device) {
        // On utilise un objet temporaire car blueSocket et blueDevice sont "final"
        BluetoothSocket tmp = null;
        blueDevice = device;

        // On récupère un objet BluetoothSocket grâce à l'objet BluetoothDevice
        try {
            // MON_UUID est l'UUID (comprenez identifiant serveur) de l'application. Cette valeur est nécessaire côté serveur également !
            tmp = device.createRfcommSocketToServiceRecord(MON_UUID);
        } catch (IOException e) { }
        blueSocket = tmp;
    }

    public void run() {
        // On annule la découverte des périphériques (inutile puisqu'on est en train d'essayer de se connecter)
        blueAdapter.cancelDiscovery();

        try {
            // On se connecte. Cet appel est bloquant jusqu'à la réussite ou la levée d'une erreur
            blueSocket.connect();
        } catch (IOException connectException) {
            // Impossible de se connecter, on ferme la socket et on tue le thread
            try {
                blueSocket.close();
            } catch (IOException closeException) { }
            return;
        }

        // Utilisez la connexion (dans un thread séparé) pour faire ce que vous voulez
        manageConnectedSocket(blueSocket);
    }

    // Annule toute connexion en cours et tue le thread
    public void cancel() {
        try {
            blueSocket.close();
        } catch (IOException e) { }
    }
}

VI. Remerciements

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