L’objectif est d’écrire une application Android qui communique en Bluetooth pour commander des E/S à distance.
En reprenant les applications réalisées dans les activités précédentes :
Copier et renommer le dossier de la première application en MyApplication2
Renommer l’attribut package dans $HOME/AndroidStudioProjects/MyApplication2/app/src/main/AndroidManifest.xml
Renommer la valeur d’applicationId dans /home/tv/AndroidStudioProjects/MyApplication2/app/build.gradle
Démarrer Android Studio
et ouvrir le projet MyApplication2
Faire ‘Synchronize’
Refactoriser le nom du package puis renommer la valeur d’app_name dans strings.xml
si nécessaire
L’application a besoin des droits d’accès (uses-permission
) au Bluettoth.
Pour cela, il faut modifier le fichier AndroidManifest.xml
de l’application et reconstruire le projet :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tv.myapplication4">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".MyActivity" android:theme="@style/AppTheme.NoTitleBar">
</activity>
</application>
</manifest>
AndroidManifest.xml
de l’application et reconstruire le projet.L’application nécessite la présence du Bluetooth pour fonctionner. On va tout d’abord vérifier son fonctionnement avant de valider les boutons qui en dépendent.
Pour cela, on ajoute le code suivant dans la méthode onCreate()
de l’activité :
private final static int REQUEST_CODE_ENABLE_BLUETOOTH = 0;
...
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null)
{
Toast.makeText(getApplicationContext(), "Bluetooth non activé !", Toast.LENGTH_SHORT).show();
}
else
{
if (!bluetoothAdapter.isEnabled())
{
Toast.makeText(getApplicationContext(), "Bluetooth non activé !", Toast.LENGTH_SHORT).show();
// Possibilité 1 :
Intent activeBlueTooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(activeBlueTooth, REQUEST_CODE_ENABLE_BLUETOOTH);
// ou Possibilité 2:
//bluetoothAdapter.enable();
}
else
{
Toast.makeText(getApplicationContext(), "Bluetooth activé", Toast.LENGTH_SHORT).show();
}
}
// Possibilité 1 :
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != REQUEST_CODE_ENABLE_BLUETOOTH)
return;
if (resultCode == RESULT_OK)
{
Toast.makeText(getApplicationContext(), "Bluetooth activé", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(getApplicationContext(), "Bluetooth non activé !", Toast.LENGTH_SHORT).show();
}
}
La méthode getBondedDevices()
retourne la liste des périphériques connus :
private Set<BluetoothDevice> devices;
devices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice blueDevice : devices)
{
Toast.makeText(getApplicationContext(), "Device = " + blueDevice.getName(), Toast.LENGTH_SHORT).show();
}
Pour détecter de nouveaux périphériques, il va falloir être capable de recevoir des intents. Pour pouvoir recevoir des intents, Android fournit une classe BroadcastReceiver
. Ces objets sont conçus pour recevoir des Intent
et appliquer des comportements spécifiques en surchargeant la méthode onReceive()
.
Pour créer un nouveau BroadcastReceiver
, on effectue un clic droit sur le nom du package puis New -> Other -> Broadcast Receiver et on donne un nom à la classe (par exemple ReceiverBluetooth
). Le BroadcastReceiver
est alors ajouté automatiquement au fichier AndroidManifest.xml
sinon il faudra le faire manuellement.
public class ReceiverBluetooth extends BroadcastReceiver
{
public ReceiverBluetooth()
{
}
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action))
{
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(context, "Nouveau périphérique : " + device.getName(), Toast.LENGTH_SHORT).show();
}
}
}
Android fournit également un système de filtres IntentFilter
que l’on peut déclarer dans le fichier AndroidManifest.xml
ou directement dans le code source. Il y a plusieurs niveaux de filtrage (action, catégorie ou données). On enregistre le filtre avec la méthode registerReceiver()
et on le désenregistre avec unregisterReceiver()
.
receiverBluetooth = new ReceiverBluetooth();
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(bluetoothReceiver, filter);
On peut aussi créer un Broadcast Receiver directement dans le code :
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action))
{
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(getApplicationContext(), "Nouveau périphérique : " + device.getName(), Toast.LENGTH_SHORT).show();
}
}
};
On lance la recherche à partir de la méthode startDiscovery()
de BluetoothAdapter
et on l’arrête avec cancelDiscovery()
.
adaptateurBluetooth.startDiscovery();
@Override
protected void onDestroy()
{
super.onDestroy();
if (adaptateurBluetooth != null)
{
adaptateurBluetooth.cancelDiscovery();
unregisterReceiver(receiverBluetooth);
//unregisterReceiver(bluetoothReceiver);
}
}
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
public class Peripherique
{
private String nom;
private String adresse;
private Handler handler = null;
private BluetoothDevice device = null;
public Peripherique(BluetoothDevice device, Handler handler)
{
if(device != null)
{
this.device = device;
this.nom = device.getName();
this.adresse = device.getAddress();
this.handler = handler;
}
else
{
this.device = device;
this.nom = "Aucun";
this.adresse = "";
this.handler = handler;
}
// TODO
}
public String getNom()
{
return nom;
}
public String getAdresse()
{
return adresse;
}
public boolean estConnecte()
{
// TODO
return false;
}
public void setNom(String nom)
{
this.nom = nom;
}
public String toString()
{
return "\nNom : " + nom + "\nAdresse : " + adresse;
}
public void envoyer(String data)
{
// TODO
}
public void connecter()
{
// TODO
}
public boolean deconnecter()
{
// TODO
}
}
On la complètera par la suite pour la partie communication.
Dans la méthode onCreate()
de l’activité, on va intégrer la prise en charge du service Bluetooth, la détection des périphériques connus et leur affichage dans un Spinner
.
MyActivity
:@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.commande);
...
adaptateurBluetooth = BluetoothAdapter.getDefaultAdapter();
if (adaptateurBluetooth == null)
{
btnConnecter.setEnabled(false);
btnDeconnecter.setEnabled(false);
Toast.makeText(getApplicationContext(), "Bluetooth non activé !", Toast.LENGTH_SHORT).show();
}
else
{
if (!adaptateurBluetooth.isEnabled())
{
Toast.makeText(getApplicationContext(), "Bluetooth non activé !", Toast.LENGTH_SHORT).show();
Intent activeBlueTooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(activeBlueTooth, REQUEST_CODE_ENABLE_BLUETOOTH);
//bluetoothAdapter.enable();
}
else
{
Toast.makeText(getApplicationContext(), "Bluetooth activé", Toast.LENGTH_SHORT).show();
// Recherche des périphériques connus
peripheriques = new ArrayList<Peripherique>();
noms = new ArrayList<String>();
devices = adaptateurBluetooth.getBondedDevices();
for (BluetoothDevice blueDevice : devices)
{
Toast.makeText(getApplicationContext(), "Périphérique = " + blueDevice.getName(), Toast.LENGTH_SHORT).show();
peripheriques.add(new Peripherique(blueDevice, handler));
noms.add(blueDevice.getName());
btnConnecter.setEnabled(true);
btnDeconnecter.setEnabled(false);
}
if(peripheriques.size() == 0)
peripheriques.add(new Peripherique(null, handler));
if(noms.size() == 0)
noms.add("Aucun");
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, noms);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerListePeripheriques.setAdapter(adapter);
adapter.setNotifyOnChange(true);
spinnerListePeripheriques.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id)
{
//Toast.makeText(getBaseContext(), noms.get(position), Toast.LENGTH_SHORT).show();
peripherique = peripheriques.get(position);
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{
// TODO Auto-generated method stub
}
});
}
}
}
Pour assurer une communication et échanger des données, il faut un côté serveur (le périphérique qui attend et accepte la connexion) et un côté client (le périphérique qui fait la demande de la connexion).
Le smartphone jouera le rôle du client :
BluetoothSocket
avec la méthode createRfcommSocketToServiceRecord()
connect()
La communication se fera dans un thread et on aura les mêmes problèmes que pour l’activité n°3 : L’UI thread est responsable de l’affichage et des interactions avec l’utilisateur et surtout c’est le seul thread qui doit modifier l’affichage.
Peripherique
qui hérite maintenant de la classe Thread
:public class Peripherique extends Thread
{
...
private BluetoothSocket socket = null;
private InputStream receiveStream = null;
private OutputStream sendStream = null;
private TReception tReception;
public final static int CODE_CONNEXION = 0;
public final static int CODE_RECEPTION = 1;
public final static int CODE_DECONNEXION = 2;
public Peripherique(BluetoothDevice device, Handler handler)
{
if(device != null)
{
this.device = device;
this.nom = device.getName();
this.adresse = device.getAddress();
this.handler = handler;
}
else
{
this.device = device;
this.nom = "Aucun";
this.adresse = "";
this.handler = handler;
}
try
{
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
receiveStream = socket.getInputStream();
sendStream = socket.getOutputStream();
}
catch (IOException e)
{
e.printStackTrace();
socket = null;
}
if(socket != null)
tReception = new TReception(handler);
}
private class TReception extends Thread
{
Handler handlerUI;
private boolean fini;
TReception(Handler h)
{
handlerUI = h;
fini = false;
}
@Override public void run()
{
BufferedReader reception = new BufferedReader(new InputStreamReader(receiveStream));
while(!fini)
{
try
{
String trame = "";
if(reception.ready())
{
trame = reception.readLine();
}
if(trame.length() > 0)
{
Log.d(TAG, "run() trame : " + trame);
Message msg = Message.obtain();
msg.what = Peripherique.CODE_RECEPTION;
msg.obj = trame;
handlerUI.sendMessage(msg);
}
}
catch (IOException e)
{
//System.out.println("<Socket> error read");
e.printStackTrace();
}
try
{
Thread.sleep(250);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public void arreter()
{
if(fini == false)
{
fini = true;
}
try
{
Thread.sleep(250);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
Remarque : on utilise la classe Handler
pour assurer l’affichage dans l’IHM (cf. l’activité n°2).
connecter()
, deconnecter()
et envoyer()
:public void connecter()
{
new Thread()
{
@Override public void run()
{
try
{
socket.connect();
Message msg = Message.obtain();
msg.arg1 = CODE_CONNEXION;
handler.sendMessage(msg);
tReception.start();
}
catch (IOException e)
{
System.out.println("<Socket> error connect");
e.printStackTrace();
}
}
}.start();
}
public boolean deconnecter()
{
try
{
tReception.arreter();
socket.close();
return true;
}
catch (IOException e)
{
System.out.println("<Socket> error close");
e.printStackTrace();
return false;
}
}
public void envoyer(String data)
{
if(socket == null)
return;
new Thread()
{
@Override public void run()
{
try
{
if(socket.isConnected())
{
sendStream.write(data.getBytes());
sendStream.flush();
}
}
catch (IOException e)
{
System.out.println("<Socket> error send");
e.printStackTrace();
}
}
}.start();
}
public void onClick(View v)
{
if(v.getId() == R.id.buttonConnecter)
{
peripherique.connecter();
}
if(v.getId() == R.id.buttonDeconnecter)
{
if(peripherique.deconnecter())
{
btnConnecter.setEnabled(true);
btnDeconnecter.setEnabled(false);
}
}
gererBoutons(v);
}
Écrire la méthode gererBoutons()
qui permet d’envoyer une chaîne de caractères au périphérique serveur en fonction du ToogleButton
sélectionné.
Tester avec la carte fournie :