APPLICATIONS JAVA Android Partie IV Ivan MADJAROV - 2014 Applications Java sous Android IvMad, 2011-2014 4. Application réseaux, Bluetooth, Wi-Fi, TCP, Géolocalisation Android L'objectif principal de ce cours est de découvrir la programmation sous Android, sa plate-forme de développement et les spécificités du développement embarqué sur téléphone mobile. Le cours s’inspire, reprend, modifie et enrichi des supports disponibles sur Internet. 2 Applications Java sous Android IvMad, 2011-2014 3 Android : LogCat view • Le développement pose toujours le problème de tester l'application avant sa mise en "service". La View de l'application est en mode graphique. Les logs permettent l'affichage en mode texte dans la fenêtre du LogCat. • Pour afficher les opérateurs 'log' dans Eclipse il faut activer le LogCat view: • Window->Show View->Other...->LogCat. • Pour écrire un opérateur 'Log' il faut importer la classe android.util.Log qui propose les méthodes Log.i() "Info", Log.d() "Debug", Log.w() "Warning", Log.e() "Error" . • Exemple: • Log.i("NameActivity", "Bonjour, ça marche!"); Applications Java sous Android IvMad, 2011-2014 4 Android : Bluetooth (1) • Bluetooth est un protocole d'interconnexion à de courtes distances, de type "peer-to-peer" avec une bande passante faible. La communication est cryptée entre les périphériques appariés. L'API Bluetooth permet de scanner et de lier les appareils entre eux et de transférer des données. • Les connexions Bluetooth sont gérées par les classes suivantes : • BluetoothAdapter : est l'unité locale où l'application Bluetooth est lancée. • BluetoothDevice : est le périphérique distant avec lequel on cherche à communiquer. • BluetoothSocket : fait une demande de connexion au périphérique distant par l'appel de la méthode createRfcommSocketToServiceRecord. • BluetoothServerSocket : installe un Socket Bluetooth serveur pour écouter les demandes de connexion entrantes en utilisant la méthode listenUsingRfcommWithServiceRecord. Applications Java sous Android IvMad, 2011-2014 5 Android : Bluetooth (2) • Pour s'assurer que le périphérique possède le Bluetooth on procéder à une vérification rapide en instanciant la classe BluetoothAdapter. Le retour de son objet va indiquer la présence ou non de cette option. BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) Toast.makeText(BluetoothActivity.this, "Pas de Bluetooth!", Toast.LENGTH_SHORT).show(); else Toast.makeText(BluetoothActivity.this,"Le Bluetooth est disponible", Toast.LENGTH_SHORT).show(); • Pour autoriser l'opération, il faut ajouter la permission d'accéder aux API Bluetooth en ajoutant la ligne suivante dans le fichier AndroidManifest.xml : <uses‐permission android:name="android.permission.BLUETOOTH"/> Applications Java sous Android IvMad, 2011-2014 6 Android : Bluetooth (3) • Le Bluetooth peut être disponible sur l'appareil mais non activé. On peut demander l'autorisation à l'utilisateur d'activer cette option. Pour cela, on appelle la méthode startActivityForResult avec un paramètre d'Intent BluetoothAdapter.ACTION_REQUEST_ENABLE. On vérifie que le Bluetooth n'est pas activé et on demande son activation : private final static int BLUETOOTH_ACTIVATION = 1; BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); ........................................ if (!bluetoothAdapter.isEnabled() { startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), BLUETOOTH_ACTIVATION); } • Un dialog-box incitera l'utilisateur d'accepter ou non l'activation du Bluetooth sur son appareil. Le résultat de sa décision est récupérable par la méthode onActivityResult. Applications Java sous Android IvMad, 2011-2014 7 Android : Bluetooth (4) • On surcharge la méthode onActivityResult pour savoir si le Bluetooth est activé ou non. La méthode est appelée à la sortie de la boite de dialogue @Override protected void onActivityResult(int requestCode,int resultCode,Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == BLUETOOTH_ACTIVATION) { if (resultCode == RESULT_OK) { Toast.makeText(BluetoothActivity.this, "Bluetooth est activé", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(BluetoothActivity.this, "Bluetooth non activé", Toast.LENGTH_SHORT).show(); } } } Applications Java sous Android IvMad, 2011-2014 Android : Bluetooth (5) • Si on rend l'appareil Bluetooth détectable cela permet à d'autres appareils de le découvrir et de se connecter par la suite. Pour cela, on utilise la méthode startActivityForResult avec le paramètre Intent approprié : startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), BLUETOOTH_SCAN); • On obtient la liste des appareils déjà liés avec getBoundedDevices. Set<BluetoothDevice> knownDevices = bluetoothAdapter.getBoundedDevices(); for (BluetoothDevice device : knownDevices) { Log.v("BluetoothActivity", "appareil = " + devices.getName()); } • Set : interface de collection pour des objets qui n'autorisent pas des doublons dans l'ensemble, existe au moins un nul (un tableau d'objets). • for-each : boucle qui accède à chaque élément d'une collection d'objets comme dans un tableau (eg, ArrayList). 8 Applications Java sous Android IvMad, 2011-2014 9 Android : Bluetooth (6) • Le code complet (étudiez les instructions 'Set' et 'for') : public class BluetoothDeviceListActivity extends Activity { private final static int BLUETOOTH_SCAN = 1; String s = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE),BLUETOOTH_SCAN); Set<BluetoothDevice> knownDevices = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : knownDevices) { s += "appareil = " + device.getName(); } Toast.makeText(BluetoothDeviceListActivity.this,"Les Bluetooth liés: "+s,Toast.LENGTH_SHORT).show(); } } Applications Java sous Android IvMad, 2011-2014 10 Android : Bluetooth (7) • La recherche d'appareils inconnus est un traitement asynchrone et gourmant en energie effectué par le Broadcast Receiver. • Android permet de créer une classe qui implémente BroadcastReceiver pour recevoir des Intents et appliquer des comportements spécifiques au code. • L’interface BroadcastReceiver possède une seule méthode onReceive() qu'on doit implémenter. 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(BluetoothActivity.this, "New Device = " + device.getName(), Toast.LENGTH_SHORT).show(); } } }; Applications Java sous Android IvMad, 2011-2014 11 Android : BT (8) Mettre tout ensemble Ajouter les permissions dans le fichier manifest.xml Applications Java sous Android IvMad, 2011-2014 12 Android : Wi-Fi (1) • Sous Android le Wi-Fi est géré par un WifiManager. Le WifiManager représente un Android Wi-Fi Connectivity Service. Il est capable de configurer une connexion Wi-Fi, de gérer une connexion en cours, de scanner pour des points d'accès et d'enregistrer tout changement dans une connexion Wi-Fi. • Le Wi-FiManager utilise la méthode getSystemService en précisant le type de service en constante: Context.WIFI_SERVICE String service = Context.WIFI_SERVICE; WifiManager wifi = (WifiManager)getSystemService(service); • Pour autoriser l'utilisation du Wi-FiManager les paramètres des permissions pour accès et modification doivent être réglés dans le fichier manifest du projet. <uses‐permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses‐permission android:name="android.permission.CHANGE_WIFI_STATE"/> Applications Java sous Android IvMad, 2011-2014 13 Android : Wi-Fi (2) • Avec le Wi-FiManager on peut notamment activer ou désactiver la fonction Wi-Fi dans l'appareil par la méthode setWifiEnabled, obtenir le statut actuel du Wi-Fi par la méthode getWifiState ou vérifier si le Wi-Fi est activé avec la méthode isWifiEnabled. if (!wifi.isWifiEnabled()) if (wifi.getWifiState() != WifiManager.WIFI_STATE_ENABLING) wifi.setWifiEnabled(true); • La méthode getWifiState() retourne un entier entre 0 et 4 pour indiquer la situation en cours du WiFi de l'appareil : 0 1 2 3 4 ‐ ‐ ‐ ‐ ‐ WIFI_STATE_DISABLING WIFI_STATE_DISABLED WIFI_STATE_ENABLING WIFI_STATE_ENABLED WIFI_STATE_UNKNOWN Applications Java sous Android IvMad, 2011-2014 14 Android : Wi-Fi (3) • Si on met tout ensemble on peut vérifier l'état de notre appareil : public class WiFiStateActivity extends Activity { String[] wifiState = {"WIFI_STATE_DISABLING", "WIFI_STATE_DISABLED", "WIFI_STATE_ENABLING", "WIFI_STATE_ENABLED", "WIFI_STATE_UNKNOWN"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String service = Context.WIFI_SERVICE; WifiManager wifi = (WifiManager)getSystemService(service); Toast.makeText(this, "Wi‐Fi : " + wifiState[wifi.getWifiState()], Toast.LENGTH_LONG).show(); } } Applications Java sous Android IvMad, 2011-2014 15 Android : Wi-Fi (4) • Pour obtenir des informations détaillées sur une connexion il faut se référer à la méthode getConnectionInfo de la classe WifiInfo. • Cette classe offre un certain nombre de méthodes qui apportent des informations importantes sur les paramètres du réseau Wi-Fi: • getSSID : Retourne l'identificateur du réseau 802.11 en cours; • getBSSID() : Retourne l'identificateur de base de ce réseau; • getMacAddress() : Retourne l'adresse MAC de l'appareil; • getIpAddress() : Retourne l'adresse IP de l'appareil en format 'int'. Une conversion en format 'String' est alors nécessaire. • getLinkSpeed() : Retourne le débit en Mbps • getRssi() : Retourne le niveau de puissance reçu du réseau 802.11 connecté. Applications Java sous Android IvMad, 2011-2014 Android : Wi-Fi (5) • Afficher les éléments d'une connexion Wi-Fi en ajoutant les composants dans un StringBuilder par la méthode append() 16 Applications Java sous Android IvMad, 2011-2014 17 Android : Wi-Fi (6) • • • • • Avec le Wi-FiManager on peut procéder à la recherche des hotspot (bornes Wi-Fi) dans le voisinage par la méthode startScan. Pour effectuer cette opération il faut utiliser un Broadcasr Receiver avec un Intent SCAN_RESULTS_AVAILABLE_ACTION passé en paramètre. Cela assure un traitement asynchrone et la prise du résultat quand le scan a terminé. On appelle la méthode getScanResults pour obtenir les résultats sous la forme d'une liste d'objets ScanResult. Chaque objet du type ScanResult comporte les détails de la connexion repérée. Le résultat du Scan est récupéré dans un objet de type List<E>. C'est une collection d'éléments indexés à partir de zéro. List<ScanResult> results = wifi.getScanResults(); Applications Java sous Android IvMad, 2011-2014 Android : Wi-Fi (7) • Retourne le nombre de hotspot détectés et le SSID avec le plus fort signale à proximité 18 Applications Java sous Android IvMad, 2011-2014 19 Java Android : Géolocalisation (1) • En général une unité mobile Android propose des moyens de déterminer sa géolocalisation: • Par le module GPS (Global Positioning System), • Par triangulation des cellules mobiles (3G), • Par le réseau Wifi. • Pour une géolocalisation on implémente l'interface LocationListener. • On s'adresse ensuite à l’objet LocationManager pour gérer l'abonnement aux mises à jour des coordonnées GPS. • Dans l'Activité (View) on surcharge 4 méthodes : • onProviderEnabled est appelée quand une source de localisation est activée; • onProviderDisabled est appelée quand une source de localisation est désactivée; • onStatusChanged est appelée quand le statuts d’une source change; • onLocationChanged est appelée quand les coordonnées GPS changent. Applications Java sous Android IvMad, 2011-2014 20 Java Android : Géolocalisation (2) • Pour s’abonner à la mise à jour des coordonnées GPS, il faut utiliser la méthode requestLocationUpdates(String, long, float, LocationListener) qui possède 4 arguments : • Le provider utiliser pour recevoir les mises à jour des coordonnées utilisateurs (GPS / NETWORK …) • L'intervalle minimum entre deux notifications (en millisecondes) • L'intervalle minimum entre deux notifications (en mètre) • L’instance du LocationListener • Il est conseillé de s’abonner aux mises à jour des coordonnées GPS dans la méthode onResume et de se désabonner dans la méthode onStop afin de stopper l’utilisateur des ressources de localisation alors que l’application n’en a plus l’utilité, sinon le processus de mise à jour continu est la consommation de la batterie est importante. Applications Java sous Android IvMad, 2011-2014 21 Java Android : Géolocalisation (3) • Pour trouver sa position il faut activer le service et le Manager qui le gère : String serviceString = Context.LOCATION_SERVICE; LocationManager locationManager = (LocationManager)getSystemService(serviceString); • Le fichier AndroidManifest.xml doit être enrichi avec les permissions requises pour accéder aux services de localisation, Internet et au GPS: <uses‐permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses‐permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses‐permission android:name="android.permission.INTERNET" /> Applications Java sous Android IvMad, 2011-2014 22 Java Android : Géolocalisation (4) L'activité teste les 'Providers' disponibles Applications Java sous Android IvMad, 2011-2014 23 Java Android : Géolocalisation (5) import android.app.Activity; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.widget.Toast; public class SimpleGeoLocalisationActivity extends Activity { private LocationManager locationManager; private LocationListener locationListener; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple_geo_localisation); // On s'adresse à la classe LocationManager pour obtenir localisation GPS locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); locationListener = new MyLocationListener(); // Géolocalisation par GPS locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); Applications Java sous Android IvMad, 2011-2014 Java Android : Géolocalisation (6) // Géolocalisation par les cellules du réseau GSM, précision 500m // locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener); } public void onLocationChanged(Location loc) { if (loc != null) { Toast.makeText(getBaseContext(), "Localisation actuelle Lat: " + loc.getLatitude() + " Lng: " + loc.getLongitude() + " Alt: " + loc.getAltitude() + " Prec: " + loc.getAccuracy(), Toast.LENGTH_LONG).show(); } } public void onProviderDisabled(String provider) { } public void onProviderEnabled(String provider) { } public void onStatusChanged(String provider, int status,Bundle extras) { } } } 24 Applications Java sous Android IvMad, 2011-2014 25 Java Android : Géolocalisation (7) Votre position en: Latitude, Longitude, Altitude Applications Java sous Android IvMad, 2011-2014 26 Android : StrictMode • Dans une application Android, on doit éviter d'effectuer des opérations lentes sur le thread de l'interface utilisateur (GUI). • Les opérations lecture et écriture de fichiers et l'accès au réseau sont considérées comme lentes, car le temps d'aboutir est indéfini, voir imprévisible. • StrictMode est configuré pour une sécurité accrue, c.à.d. pour éviter de faire des choses incorrectes. L'exception NetworkOnMainThreadException est provoqué si l'accès réseau est effectué de l'interface utilisateur (le thread principal de l'application). • A partir de l'Android 3.0 on peut désactiver cette option pour faire des tests plus facilement sur l'accès réseau en plaçant dans la méthode onCreate() le code: StrictMode.ThreadPolicy policy = new StrictMode. ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); Applications Java sous Android IvMad, 2011-2014 27 Android : client-side TCP socket package ivmad.TCP.Client; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.InetAddress; import java.net.UnknownHostException; import android.app.Activity; import android.os.Bundle; import android.os.StrictMode; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; public class ClientTCPAndroidActivity extends Activity { LinearLayout layout; EditText textOut; TextView textIn; Button buttonSend; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); textIn = new TextView(this); textIn.setText("Message à soumettre"); textOut = new EditText(this); buttonSend = new Button(this); buttonSend.setText("Envoyer"); buttonSend.setOnClickListener(buttonSendOnClickListener); layout.addView(textIn); layout.addView(textOut); layout.addView(buttonSend); setContentView(layout); } Applications Java sous Android IvMad, 2011-2014 Android : client-side TCP socket Button.OnClickListener buttonSendOnClickListener = new Button.OnClickListener() { public void onClick(View v) { Socket socket = null; DataOutputStream dataOutputStream = null; DataInputStream dataInputStream = null; InetAddress serverAddr; String serverIpAddress = "192.168.0.141"; // Définir les droits d'accès au ressources réseaux StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); try { serverAddr = InetAddress.getByName(serverIpAddress); socket = new Socket(serverAddr, 1234); dataOutputStream = new DataOutputStream(socket.getOutputStream()); dataInputStream = new DataInputStream(socket.getInputStream()); dataOutputStream.writeUTF(textOut.getText().toString()); textIn.setText(dataInputStream.readUTF()); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null && dataOutputStream != null && dataInputStream != null) { try { socket.close(); dataOutputStream.close(); dataInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }; } 28 Applications Java sous Android IvMad, 2011-2014 29 Android : client-side TCP socket • Le fichier AndroidManifest.xml contient la description des ressources et les autorisations d'accès au réseau Internet L'autorisation d'accéder au réseau et configurée dans le Manifest de l'application <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ivmad.TCP.Client" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".ClientTCPAndroidActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> Applications Java sous Android IvMad, 2011-2014 30 Android : client-side TCP socket Le serveur TCP reçoit le message du client Android Le client TCP basé Android envoie un message au Serveur basé TCP Applications Java sous Android IvMad, 2011-2014 31 Android : client HTTP • Avant de procéder à une connexion réseau, il faut s'assurer que cette connexion est disponible. Un téléphone portable, un Smartphone ou une tablette peut être hors réseau ou connexion Wifi désactivée. • Cette disponibilité est testée avec les méthodes getActiveNetworkInfo() et isConnected(). • La classe ConnectivityManager détecte les connexions Wifi, GPRS, UMTS, etc. public boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); // Si le réseau est indisponible networkInfo = null et la // méthode retourne false, sinon true. if (networkInfo != null && networkInfo.isConnected()) { return true; } return false; } Applications Java sous Android IvMad, 2011-2014 32 Android : HTTP connection • HttpURLConnection avec URL sont des classes disponibles depuis Java 2.0 standard. • C'est un client léger d'usage général basé HTTP et approprié pour la plupart des applications Android. try { URL url = new URL("http://ivmad.free.fr/ic4"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); // méthode qui lit un contenu sur le Stream d'entrée readStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } Applications Java sous Android IvMad, 2011-2014 33 Android : HTTP connection • Lecture du Stream d'entrée dont la connexion est effectuée par HttpURLConnection et URL. private void readStream(InputStream in) { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(in)); String line = "", str = ""; while ((line = reader.readLine()) != null) { str += line; } Toast.makeText(getApplicationContext(),str,Toast.LENGTH_LONG).show(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { reader.close(); } } }