Développement sous Android

Dimension: px
Commencer à balayer dès la page:

Download "Développement sous Android"

Transcription

1 Développement sous Android Chapitres traités Les fournisseurs de contenu (Content Provider) Pour cette dernière étude de l'environnement Android, nous allons en profiter pour découvrir plein de fonctionnalités intéressantes comme le partage des données, les services, la communication réseau, la prise en compte des capteurs intégrés, la géolocalisation, le graphisme, le multimédia, etc. Les fournisseurs de contenu (Content Provider) Vous pouvez décider d'étendre votre application en proposant des extensions utilisant les mécanismes de partage de données mis à disposition par un service déjà installé sur le téléphone de l'utilisateur. Le partage des données Android via les fournisseurs de contenu est un excellent moyen de diffuser de l'information selon une interface standard d'échange. L'utilisation des bases de données vous permet de stocker des données complexes et structurées. L'accès à une base de données n'est possible que depuis l'application à partir de laquelle elle a été créée. Si vous souhaitez exposer les données d'une application à d'autres applications, par exemple des photos prises par votre application, Android prévoit un mécanisme plutôt élégant pour parvenir à cette fin : la notion de fournisseur de contenu, sous la forme d'une interface générique permettant d'exposer les données d'une application en lecture et/ou en écriture. Ce mécanisme permet de faire une scission claire entre votre application et les données exposées. Ainsi, en rendant vos données disponibles au travers d'une telle interface, vous rendez votre application accessible et extensible à d'autres applications en tant que fournisseur de contenu, que ces applications soient créées par vous-même ou des tiers. Les URIs Avec Android, toute URI de schéma content:// représente une ressource servie par un fournisseur de contenu. Les fournisseurs de contenu encapsulent les données en utilisant des instances d'uri comme descripteur. Nous ne savons jamais d'où viennent les données représentées par l'uri et nous n'avons pas besoin de le savoir : la seule chose qui compte est qu'elles soient disponibles lorsque nous en avons besoin. Ces données pourraient être stockées dans une base de données SQLite ou dans des fichiers plats, voire récupérées à partir d'un terminal ou stockées sur un serveur situé loin d'ici, sur Internet. A partir d'une URI, vous pouvez réaliser les opérations CRUD de base (Create, Read, Update, Delete) en utilisant un fournisseur de contenu. Les instances d'uri peuvent représenter des collections ou des éléments individuels. Grâce aux premières, vous pouvez créer de nouveaux contenus via des opérations d'insertion. Avec les secondes, vous pouvez lire les données qu'elles représentent, les modifier ou les supprimer. Composantes d'une URI Un fournisseur de contenu fonctionne un peu à la manière d'un service web accessible en REST (que nous verrons lors de cette étude) : vous exposez un ensemble d'uri capable de vous retourner différents ensembles d'éléments (tous les éléments, un seul ou un sous-ensemble) en fonction de l'uri et des paramètres. Quand une requête cible un fournisseur de contenu, c'est le système qui gère l'instanciation de celui-ci. Un développeur n'aura jamais à instancier les objets d'un fournisseur de contenu lui-même. Le modèle simplifié de construction d'une URI est constitué du schéma, de l'espace de noms des données et, éventuellement, de l'identifiant de l'instance. Ces différents composants sont séparés par des barres de fraction, comme dans une URL. content://constants/5. Cette URI représente une instance constants d'identifiant 5. La combinaison du schéma et de l'espace de noms est appelé URI de base d'un fournisseur de contenu ou d'un ensemble de données supporté par un fournisseur de contenu. Dans l'exemple précédent, content://contants est l'uri de base d'un fournisseur de contenu qui sert des informations sur "contants" (en l'occurence, des constantes physiques). L'URI de base peut être plus compliquée. Celle des contacts est, par exemple, content://com.android.contacts/contacts, car le fournisseur de contenu des contacts peut fournir d'autres données en utilisant d'autres valeurs pour l'uri de base. L'URI de base représente une collection d'instances. Combinée avec un identifiant d'instance (5, par exemple), elle représente une instance unique. La plupart des API d'android s'attendent à ce que les URI soient des objets de type Uri, bien qu'il soit plus naturel de les considérer comme des chaînes. La méthode statique Uri.parse() permet ainsi de créer une instance d'uri à partir de sa représentation textuelle. D'où viennent ces instances d'uri? Le point de départ le plus courant, lorsque nous connaissons le type de données avec lequel nous souhaitons travailler, consiste à obtenir l'uri de base du fournisseur de contenu lui-même. CONTENT_URI, par exemple, est l'uri de base des contacts représentés par des personnes, elle correspond à content://com.android.contacts/contacts. Si vous avez simplement besoin de la collection, cette URI fonctionne telle quelle ; si vous avez besoin d'une instance dont vous

2 connaissez l'identifiant, vous pouvez l'ajouter à la fin de cette dernière, afin d'obtenir une URI pour cette instance précise. Accéder à un fournisseur de contenu Pour accéder à un fournisseur de contenu, vous devrez utiliser la classe android.content.contentresolver. Cette classe est un véritable utilitaire qui sera votre principal point d'accès vers les fournisseurs de contenu Android. Portez également une attention particulière à l'espace de noms android.provider dans lequel vous trouverez un ensemble de classes facilitant l'accès aux fournisseurs de contenu natifs de la plate-forme Android (appels, contacts, etc.). Vous pouvez récupérer une instance - unique pour l'application - de la classe ContentResolver eu utilisant la méthode getcontentresolver(). ContentResolver fournisseur = getcontentresolver(); Chaque fournisseur de contenu expose publiquement une propriété CONTENT_URI, qui stocke l'uri de requête du contenu, comme le montre les fournisseurs de contenu natifs d'android suivants : android.provider.calllog.calls.content_uri android.provider.calendar.content_uri android.provider.mediastore.images.media.external_content_uri Effectuer une requête Tout comme les bases de données abordées dans l'étude précédente, les fournisseurs de contenu retournent leurs résultats sous la forme d'un Cursor. Du coup, vous pouvez effectuer les mêmes opérations que lorsque vous manipuliez des bases de données, l'utilisation dans le cadre d'un fournisseur de contenu ne limite en rien ses fonctionnalités. Une requête s'effectuera en utilisant la méthode query() du ContentResolver en passant les paramètres listés dans le tableau suivant : 1. uri : L'URI du fournisseur de contenu. 2. projection : La projection, soit les colonnes que vous souhaitez voir retournées. Cette valeur peut aussi être null. 3. where : Une clause pour filtrer les éléments retournés. Dans cette chaîne de caractères, l'utilisation du '?' est possible ; chacun de ces caractères sera remplacé par les valeurs du paramètre de sélection (le paramètre suivant). Cette valeur peut aussi être égale à null. 4. selection : Un tableau de sélections qui remplaceront les caractères '?' placés dans la clause where. Ceci permet de rendre dynamiques les requêtes de façon à ne pas avoir à manipuler une chaîne de caractères pour construire la requête mais de le faire via l'ensemble de valeurs d'un tableau rempli dynamiquement à l'exécution. Cette valeur peut aussi être null. 5. order : le nom de la colonne associée à une chaîne de tri ('DESC' ou 'ASC'). Cette valeur peut aussi être égale à null. // Requêter toutes les données du fournisseur de contenu ContentResolver fournisseur = getcontentresolver(); Cursor données = fournisseur.query(monfournisseur.content_uri, null, null, null, null); // Filtrer les données retournées et trier par ordre croissant sur le nom ContentResolver fournisseur = getcontentresolver(); String[] projection = new String[] {"nom", "prénom", "âge"; String filtre = "prénom = Julien"; String ordre = "nom ASC"; Cursor données = fournisseur.query(monfournisseur.content_uri, projection, filtre, null, ordre); 1. Une fois le curseur récupéré, vous opérez de la même façon qu'avec une base de données. 2. La partie concernant la création d'un fournisseur de contenu expliquera plus en détail comment fonctionne la paramètre URI d'une requête. 3. Pour restreindre la requête à un élément en particulier, vous pouvez ajouter la valeur de son identifiant (id) correspondant. par exemple, si celui-ci est 10, l'uri sera content://fr.btsiris.db/10. Il existe plusieurs méthodes d'aide, telles que ContentUris.withAppendedId() et Uri.withAppendPath(), qui vous faciliteront la vie. pour continuer notre exemple, le code correspondant pour construire la requête adéquate permettant de ne cibler qu'un élément en particulier s'écrit ainsi : Uri requête = Uri.parse("content://fr.btsiris.bd"); Uri requêteparticulière = ContentUris.withAppendedId(requête, 10); Uri requêteparticulière = Uri.withAppendPath(requête, "10"); Cursor données = managedquery(requêteparticulière, null, null, null, null); Les fournisseurs de contenu natifs Android possède quelques fournisseurs de contenu disponibles nativement permettant d'exposer certaines informations contenues dans les bases de données du système. 1. Contacts : Retrouvez, ajoutez ou modifiez les informations des contacts. 2. Magasin multimédia : Le magasin multimédia stocke l'ensemble des fichiers multimédias de votre appareil. Ajouter ou supprimez les fichiers multimédias depuis ce fournisseur de contenu.

3 3. Navigateur : Accédez à l'historique, aux marque-pages ou aux recherches. 4. Appels : Accédez à l'historique des appels entrants, sortants et manqués. 5. Paramètres : Accédez aux paramètres système - sonnerie, etc. - pour lire ou modifier certaines préférences. Les concepteurs de la plate-forme Android l'ont bien compris, pouvoir accéder librement aux contacts d'un appareil de téléphonie est une aubaine pour bon nombre d'applications. En accordant les permissions adéquates, un développeur peut effectuer une requête, modifier, supprimer ou encore ajouter un contact. L'exemple suivant permet de lire l'ensemble des contacts stockés dans l'appareil : package fr.btsiris.fournisseur; fr.btsiris.fournisseur.fournisseurcontenu.java import android.app.*; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.provider.contactscontract; import android.widget.*; public class FournisseurContenu extends ListActivity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); Uri requête = ContactsContract.Contacts.CONTENT_URI; String ordre = ContactsContract.Contacts.DISPLAY_NAME +" COLLATE LOCALIZED ASC"; Cursor lignes = getcontentresolver().query(requête, null, null, null, ordre); startmanagingcursor(lignes); ListAdapter liste = new SimpleCursorAdapter(this, android.r.layout.two_line_list_item, lignes, new String[] {ContactsContract.Data.DISPLAY_NAME, new int[] {android.r.id.text1); setlistadapter(liste); <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" /> res/layout/main.xml La plupart des fournisseurs de contenu ne donnent accès à leurs contenus qu'avec des permissions. Par conséquent, n'oubliez pas d'ajouter la permission adéquate à l'application lors de la lecture de ce fournisseur de contenu, comme dans l'exemple ci-dessous : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.fournisseur" android:versioncode="1" android:versionname="1.0"> <application > <activity android:name="fournisseurcontenu" AndroidManifest.xml

4 <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.read_contacts" /> </manifest> Ajouter, supprimer ou mettre à jour des données via le fournisseur de contenu Les fournisseurs ne sont pas seulement des points d'exposition pour permettre aux applications d'effectuer des requêtes sur les bases de données d'autres applications. Ils permettent aussi de manipuler les données exposées en termes d'ajout, de modification et de suppression. Toutes ces actions s'effectuent toujours à l'aide de la classe ContentResolver. 1. Ajouter des données : Pour insérer des données, la classe ContentResolver propose deux méthodes : insert() et bulkinsert(). La première permet d'insérer une seule donnée et la seconde d'insérer un jeu de données en un seul appel. // Créer un objet contenant les valeurs de la donnée à ajouter ContentResolver fournisseur = getcontentresolver(); ContentValues valeur = new ContentValues(); valeur.put("nom", "REMY"); valeur.put("prénom", "Emmanuel"); Uri nouvellevaleur = fournisseur.insert(monfournisseur.content_uri, valeur); // Insérer des données en masse dans un fournisseur de contenu ContentResolver fournisseur = getcontentresolver(); ContentValues[ ] valeurs = new ContentValues[5]; valeurs[0] = new ContentValues(); valeurs[0].put("nom", "REMY"); valeurs[0].put("prénom", "Emmanuel"); valeurs[1] = new ContentValues(); valeurs[1].put("nom", "ALBIN"); valeurs[1].put("prénom", "Michel");... int nombrevaleurs = fournisseur.bulkinsert(monfournisseur.content_uri, valeurs); 2. Mettre à jour les données : Vous avez trouvé les éléments qui vous intéressent depuis le fournisseur de contenu et vous souhaitez les mettre à jour? Pour cela, la classe ContentResolver met à votre disposition la méthode update(). Celle-ci prend en paramètres une valeur ContentValues spécifiant les valeurs de chaque colonne de l'entrée ainsi qu'un paramètre de filtre pour spécifier quel ensemble de données est concernée par cette mise à jour. Tous les éléments répondant à la condition du filtre sont mis à jour et la valeur que retourne la méthode update() est le nombre d'éléments ayant été mis à jour avec succès. // Mise à jour des éléments du fournisseur de contenu ContentResolver fournisseur = getcontentresolver(); ContentValues valeur = new ContentValues(); valeur.put("nom", "REMY"); valeur.put("prénom", "Emmanuel"); String filtre = "nom = REMY" ; int nombrevaleurs = fournisseur.update(monfournisseur.content_uri, valeur, filtre, null); 3. Supprimer des données : Tout comme le curseur de base de données, vous avez la possibilité de supprimer des éléments directement depuis le fournisseur de contenu avec la méthode delete(). Vous pouvez supprimer une seule valeur, celle ciblée par l'uri, ou plusieurs valeurs en spécifiant un paramètre de filtre. // Suppression de l'élément possédant un id de 10 ContentResolver fournisseur = getcontentresolver(); Uri requête = Uri.parse("content://fr.btsiris.bd"); Uri recherche = ContentUris.withAppendedId(requête, 10); fournisseur.delete(recherche, null, null); // Suppression de tous les éléments du fournisseurs de contenu dont le prénom est 'Julien' String filtre = "prénom = Julien" ; fournisseur.delete(requête, filtre, null); Créer un fournisseur de contenu La création d'un fournisseur de contenu est d'une importance capitale lorsqu'une application souhaite mettre ses données à disposition d'autres applications. Si elle ne les garde que pour elle-même, vous pouvez éviter la création d'un fournisseur de contenu en vous contentant d'accéder directement aux données depuis vos activités. Un fournisseur de contenu est une classe Java, tout comme une activité est une intention, la principale étape de la création d'un fournisseur consiste à produire sa classe Java, qui doit hériter de ContentProvider. Cette sous-classe doit implémenter six méthodes qui, ensemble, assurent le service qu'un fournisseur de contenu est censé offrir aux activités qui veulent créer, lire, modifier ou supprimer du contenu. La création d'un fournisseur de contenu s'effectue en dérivant une classe de ContentProvider et en redéfinissant les méthodes du tableau cidessous en fonction de vos besoin :

5 1. oncreate() : Obligatoire. Appelée lors de la création du fournisseur de contenu. Renvoie un booléen pour spécifier que le fournisseur de contenu a été chargé avec succès. 2. query() : Requête sur le fournisseur de contenu. Retourne un curseur permettant au programme local l'ayant appelé de naviguer au sein des résultats retournés. 3. delete() : Suppression d'une ou plusieurs lignes de données. L'entier retourné représente le nombre de lignes supprimées. 4. insert() : Appelée lorsqu'une insertion de données doit être réalisée sur le fournisseur de contenu. L'URI renvoyée correspond à la ligne nouvellement insérée. 5. update() : Mise à jour d'une URI. Toutes les lignes correspondant à la sélection seront mises à jour avec les valeurs fournies dans l'objet de type ContentValues des paramètres. 6. gettype() : Retourne le type de contenu MIME à partir de l'uri spécifiée en paramètre. La méthode query() retourne un objet Cursor vous permettant de manipuler et de naviguer dans les données de la même façon qu'avec un curseur de base de données SQLite (Cursor n'est qu'une interface). Par conséquent, vous pouvez utiliser n'importe quel curseur disponible dans le SDK Android : SQLiteCursor (base de données SQLite), MatrixCursor (pour les données non stockées dans une base de données), MergeCursor, etc. 1. De façon à identifier sans ambiguïté et pouvoir manipuler un fournisseur de conetnu, vous devez spécifier une URI unique, en général basé sur l'espace de nom de la classe d'implémentation du fournisseur. De façon à être accessible par tous les développeurs et applications, l'uri doit être exposé sous la variable publique statique nommée CONTENT_URI : "content://fr.btsiris.bd/personnes". 2. Avec cette URI, vous demanderez au fournisseur de contenu que nous allons élaborer de renvoyer toutes les personnes enregistrées actuellement dans la base de données. Si vous complétez la requête en la prefixant avec un numéro, vous demanderez uniquement la personne souhaitée à l'aide de son identifiant : "content://fr.btsiris.bd/personnes/10". 3. Le SDK d'android possède une classe UriMatcher permettant de faciliter la gestion des URI. En utilisant et configurant celle-ci, vous n'aurez pas à scinder les URI vous-même pour essayer d'en extraire le contenu, la classe UriMatcher le fera pour vous. Ce chapitre n'est pas erminé et sera traité dans un avenir proche.. Création d'un service Les services d'android sont destinés aux processus de longue haleine, qui peuvent devoir continuer de s'exécuter même lorsqu'ils sont séparés de toute activité. A titre d'exemple, citons la musique, qui continue d'être jouée même lorsque l'activité de "lecture" a été supprimée, la récupération des mises à jour des flux RSS sur Internet et la persistance d'une session de messagerie instantanée, même lorsque le client a perdu le focus à cause de la réception d'un appel téléphonique. Un service est créé lorsqu'il est lancé manuellement (via un appel à l'api) ou quand une activité tente de s'y connecter via une communication interprocessus (IPC). Il perdure jusqu'à ce qu'il ne soit plus nécessaire et que le système ait besoin de récupérer de la mémoire. Cette longue exécution ayant un coût, les services doivent prendre garde à ne pas consommer trop de CPU sous peine d'user prématurément la batterie du terminal. A la différence d'autres plates-formes du marché, Android offre un environnement pour ces applications sans interface utilisateur. Ainsi, les services sont des applications dérivant de la classe android.app.service, qui s'exécutent en arrière-plan et qui sont capables de signaler à l'utilisateur qu'un événement ou une information nouvelle requiert son attention. 1. Contrairement aux activités qui offrent une interface utilisateur, les services en sont complètement dépourvus. Ils peuvent cependant réagir à des événements, mettre à jour des fournisseurs de contenu, effectuer n'importe quel autre traitement..., et enfin notifier leur état à l'utilisateur via des toasts et des notifications. 2. Ces services, complètement invisibles pour l'utilisateur, possèdent un cycle de vie similaire à une activité et peuvent être contrôlés depuis d'autres applications (activité, service et Broadcast Receiver). Il est ainsi commun de réaliser une application composée d'activités et d'un service ; un lecteur de musique utilisera une interface de sélection des musiques alors que le service jouera celles-ci en arrière plan. 3. Les applications qui s'exécutent fréquemment ou continuellement sans nécessiter constamment une interaction avec l'utilisateur peuvent être implémentées sous la forme d'un service. Les applications qui récupèrent des informations (informations, météo, résultats sportifs, etc.) via des flux RSS/Atom représentent de bons exemples de services. Création d'un service Les services sont conçus pour s'exécuter en arrière plan et nécessitent de pouvoir être démarrés, contrôlés et stoppés par d'autres applications. Les services partagent un certain nombre de comportements avec les activités. C'est pourquoi vous retrouverez des méthodes similaires à celles abordées précédemment pour les activités. Pour créer un service, dérivez votre classe de android.app.service puis, à l'instar d'une activité, redéfinissez les méthodes du cycle de vie avant de déclarer le service dans le fichier de configuration de l'application. Les services peuvent redéfinir trois méthodes : oncreate(), onstart() et ondestroy(). Les méthodes oncreate() et ondestroy() sont respectivement appelées lors de la création et de la destruction du service, à l'intar d'une activité. La méthode onbind() est la seule méthode que vous devez obligatoirement implémenter dans un service. Cette méthode retourne un objet IBender qui permet au mécanisme IPC - communication interprocessus permettant d'appeler des méthodes distantes - de fonctionner. Une fois votre service créé, il vous faudra, comme pour les activités d'une application, déclarer celui-ci dans le fichier de configuration de l'application au moyen de la balise <service android:name=".monservice" />. Voici les paramètres supplémentaires que vous pouvez spécifier sous forme d'attributs dans l'élément <service> :

6 1. process : Spécifie un nom de processus dans lequel le code sera exécuté. 2. enabled : Indique si le service est actif ou non (peut-être instancié par le système). 3. exported : Booléen indiquant si le service est utilisable par d'autres applications. 4. permission : Spécifie une permission nécessaire pour exécuter ce service. En plus des attributions dans l'élément <service>, vous pouvez utiliser l'élément <meta-data> pour spécifier des données supplémentaires et une section <intent-filter> à l'instar des activités. Démarrer et arrêter un service Les services peuvent être démarrés manuellement (via l'appel de la méthode startservice() ou via un objet Intent) ou quand une activité essaie de se connecter au service via une communication interprocessus (IPC). 1. Pour démarrer un service manuellement, utilisez la méthode startservice(), laquelle accepte en paramètre un objet Intent. Ce dernier permet de démarrer un service en spécifiant son type de classe ou une action spécifiée par le filtre d'intention du service. // Démarre le service explicitement startservice(new Intent(this, MonService.class)); // Démarre le service en utilisant une action (enregistré dans Intent Filter). startservice(new Intent(MonService.MON_ACTION_SPECIALE)); Le moyen le plus simple de démarrer un service est de spécifier le nom de sa classe en utilisant le paramètre class. Cette option ne fonctionne que si le service est un composant de votre application et non un service tiers. Si le service à démarrer n'a pas été créé par vous, la seule alternative sera d'utiliser l'intent associé à ce service. 2. La méthode startservice() peut être appelée plusieurs fois, ce qui aura pour conséquence d'appeler plusieurs fois la méthode onstart() du service. Sachez donc gérer cette situation, de façon à ne pas allouer de ressources inutiles ou réaliser des traitements, qui rendraient caduque la bonne exécution de votre service. 3. Pour arrêter un service, utilisez la méthode stopservice() avec l'action que vous avez utilisée lors de l'appel à la méthode startservice() : stopservice(new Intent(this, monservice.getclass())); Contrôler un service depuis une activité L'exécution d'un service requiert le plus souvent d'avoir pu configuré ce dernier, souvent à l'aide d'une interface que l'utilisateur pourra utiliser pour spécifier ou changer les paramètres. Afin de pouvoir communiquer avec un service, vous avez plusieurs possibilités à votre disposition. 1. La première consiste à exploiter le plus possible le mécanisme des intentions au sein du service. 2. La seconde (dans le cas de deux applications distinctes) consiste à effectuer un couplage fort entre les processus de l'application appelante et le processus du service en utilisant le langage AIDL (Android Interface Definition Language), qui permet la définition de l'interface du service au niveau du système - ce thème ne sera pas abordé dans cette étude. 3. Il existe aussi une autre alternative qui combine ces deux possibilités en contrôlant un service à l'aide d'une activité et en les liant, à condition que le service et l'activité fassent partie de la même application. C'est cette dernière façon que nous allons détailler ci-après. Lorsqu'une activité est liée à un service, celle-ci possède une référence permanente vers le service, qui vous permet de communiquer avec le service comme si ce dernier n'était qu'une simple référence à un objet de votre activité. Pour permettre à une activité de s'associer à un service, la méthode onbind() doit être implémentée au niveau du service. Cette méthode retourne un objet qui sera utilisé plus tard par l'activité pour récupérer la référence vers le service. La méthode onbind() doit renvoyer un type IBinder propre à votre service, ce qui signifie que vous devez créer votre propre implémentation de l'interface IBinder : public class ServiceConversion extends Service {... public IBinder onbind(intent intention) { return new ConversionBinder(); public class ConversionBinder extends Binder { ServiceConversion getservice() { return ServiceConversion.this; Une fois la méthode onbind() implémentée au niveau de votre service, vous devez maintenant connecter l'activité au service. Pour ce faire, vous devez utiliser une connexion de type ServiceConnection : ce sont les méthodes de cette dernière qui seront appelées par l'activité pour se connecter ou se déconnecter du service. Créez la connexion au niveau de l'activité, en redéfinissant les méthodes onserviceconnected() et onservicedisconnected() de la classe ServiceConnection. La première méthode sera appelée lors de la connexion de l'activité au service et la seconde lors de la déconnexion. public class Conversion extends Activity { private ServiceConversion service; private ServiceConnection connexion = new ServiceConnection() { public void onserviceconnected(componentname nom, IBinder binder) {

7 service = ((ServiceConversion.ConversionBinder)binder).getService(); public void onservicedisconnected(componentname nom) { service = null; ; Une fois que la méthode onbind() du service est implémentée et qu'un objet ServiceConnection est prêt à faire office de médiateur, l'activité doit encore demander de façon explicite l'association au service, à l'aide de la méthode bindservice(). Cette méthode prend trois paramètres : 1. L'intention du service que vous souhaitez invoquer, souvent de façon explicite directement en utilisant la classe du service ; 2. Une instance de la connexion créée vers le service ; 3. Un drapeau spécifiant les options d'association, souvent 0 ou la constante BIND_AUTO_CREATE. Une autre valeur BIND_DEBUG_UNBIND permet d'afficher des informations de débogage. Vous pouvez combiner les deux valeurs à l'aide de l'opérateur binaire &. public class Conversion extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); Intent soumettre = new Intent(this, ServiceConversion.class); bindservice(soumettre, connexion, Context.BIND_AUTO_CREATE); Une fois l'association effectuée à l'aide de la méthode bindservice(), la méthode onserviceconnected() sera appelée et la référence vers le service associé sera récupérée. Vous pourrez ainsi appeler toutes les méthodes et propriétés publiques du service au travers de cette référence comme s'il s'agissait d'un simple objet de l'activité, et de cette façon contrôler le service au travers de l'interface utilisateur proposée par l'activité. Pour vous déconnecter, utilisez la méthode unbindservice() de l'instance de l'activité. A titre d'exemple, nous allons modifier un projet que nous avons déjà mis en oeuvre lors de l'étude précédente. Il s'agit de la conversion monétaire. Bien que cela ne soit pas très utile, je vous propose, comme un cas d'école, d'intégrer à ce projet un service qui s'occupera de la conversion elle-même. Au niveau du visuel, nous ne verrons aucune modification apparente si ce n'est la présence d'affichage de messages qui nous montrent le démarrage et l'arrêt du service. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.conversion" android:versioncode="1" android:versionname="1.0"> <application android:label="service en action" > <activity android:name="conversion" android:label="conversion"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <service android:name="serviceconversion" /> </application> </manifest> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:stretchcolumns="1"> <TableRow> <fr.btsiris.conversion.monnaie android:layout_span="3"/> <Button android:text="franc" android:onclick="calculfranc" /> </TableRow> <TableRow> <fr.btsiris.conversion.monnaie AndroidManifest.xml res/layout/main.xml

8 android:layout_span="3"/> <Button android:text=" uro" android:onclick="calculeuro" /> </TableRow> </TableLayout> package fr.btsiris.conversion; fr.btsiris.conversion.serviceconversion.java import android.app.*; import android.content.intent; import android.os.*; import android.widget.toast; public class ServiceConversion extends Service { private final double TAUX = ; public void oncreate() { super.oncreate(); Toast.makeText(this, "Service en action", Toast.LENGTH_LONG).show(); public void ondestroy() { super.ondestroy(); Toast.makeText(this, "Service désactivé", Toast.LENGTH_LONG).show(); public double eurofranc(double euro) { return euro * TAUX; public double franceuro(double franc) { return franc / TAUX; public IBinder onbind(intent intention) { return new ConversionBinder(); public class ConversionBinder extends Binder { ServiceConversion getservice() { return ServiceConversion.this; Dans ce cas de figure, il n'est absolument pas nécessaire de redéfinir les méthodes de rappel onstart() et ondestroy(). Je les ai placés juste pour vérifier le moment de l'activation et de la désactivation du service, comme un système de débogage. package fr.btsiris.conversion; fr.btsiris.conversion.monnaie.java import android.content.context; import android.text.inputtype; import android.util.attributeset; import android.view.gravity; import android.widget.edittext; import java.text.*; public class Monnaie extends EditText { private NumberFormat formatmonnaie; public Monnaie(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); init(); public Monnaie(Context context, AttributeSet attrs) { super(context, attrs); init(); public Monnaie(Context context) { super(context); init(); private void init() { setsymbole(' '); setvaleur(0.0); setgravity(gravity.right);

9 setinputtype(inputtype.type_class_number); public void setsymbole(char symbole) { formatmonnaie = new DecimalFormat("#,##0.00 "+symbole); public double getvaleur() { try { Number valeur = (Number) formatmonnaie.parse(gettext().tostring()); settext(formatmonnaie.format(valeur)); return valeur.doublevalue(); catch (ParseException ex) { return 0.0; public void setvaleur(double valeur) { settext(formatmonnaie.format(valeur)); package fr.btsiris.conversion; fr.btsiris.conversion.conversion.java import android.app.activity; import android.content.*; import android.os.*; import android.view.view; public class Conversion extends Activity { private Monnaie euro, franc; private ServiceConversion service; private ServiceConnection connexion = new ServiceConnection() { public void onserviceconnected(componentname nom, IBinder binder) { service = ((ServiceConversion.ConversionBinder)binder).getService(); public void onservicedisconnected(componentname nom) { service = null; ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); euro = (Monnaie) findviewbyid(r.id.euro); franc = (Monnaie) findviewbyid(r.id.franc); franc.setsymbole('f'); franc.setvaleur(0.0); Intent soumettre = new Intent(this, ServiceConversion.class); bindservice(soumettre, connexion, Context.BIND_AUTO_CREATE); protected void ondestroy() { super.ondestroy(); unbindservice(connexion); public void calculfranc(view vue) { franc.setvaleur(service.eurofranc(euro.getvaleur())); public void calculeuro(view vue) { euro.setvaleur(service.franceuro(franc.getvaleur())); Géolocalisation, Google Maps et GPS L'une des caractéristiques essentielles des téléphones mobiles est leur portabilité, et il n'est donc pas surprenant que quelques-unes des fonctionnalités les plus séduisantes d'android soient les services permettant de déterminer, de contextualiser et de cartographier des positions géographiques. Vous pouvez créer des activités fondées sur des cartes utilisant Google Maps comme un élément d'interface utilisateur. Vous aurez un accès complet aux cartes, ce qui vous permettra de contrôler les paramètres d'affichage, de changer le niveau de zoom et de faire un panoramique. Vous pourrez annoter les cartes et gérer la saisie de l'utilisateur pour produire des informations et des fonctionnalités contextuelles.

10 Nous couvrirons également dans ce chapitre les services de géolocalisation, qui permettent de déterminer la position courante de l'appareil. Ils incluent le GPS et la technologie Google de localisation cellulaire. Vous pourrez spécifier quelle technologie utiliser en la nommant explicitement ou implicitement, en définissant un ensemble de critères en termes de précision, coût ou autres. Les cartes et les services de géolocalisation utilisent la latitude et la longitude pour identifier les positions géographiques, mais la plupart des utilisateurs raisonnent plutôt en terme d'adresses. Android fournit un Geocoder qui supporte les géocodages avant et inverse. Grâce à lui, vous pourrez convertir latitudes et longitudes en adresses et inversement. Utilisés conjointement, la cartographie, le géocodage et les services de géolocalisation fournissent une puissante boîte à outils pour incorporer la mobilité native de votre téléphone à vos applications. Les services de géolocalisation d'android sont donc divisés en deux grandes parties : 1. Les API qui gèrent les plans (dans l'espace de noms com.google.android.maps) ; 2. Les API qui gèrent la localisation à proprement parler (dans l'espace de noms android.location). Nous allons d'abord voir comment déterminer la position d'un appareil sur Android. Ensuite, nous verrons en deux temps comment utiliser l'émulateur pour simuler des positions en étudiant d'abord la génération des fichiers de points et ensuite leur emploi. La suite du chapitre sera consacrée aux capacités de la plate-forme et notamment à l'affichage de cartes Google Maps ainsi qu'aux différents dessins ou affichage réalisables grâce à ces mêmes cartes. Utiliser les services de géolocalisation Les services de géolocalisation sont un terme générique désignant les différentes technologies utilisées pour déterminer la position courante d'un appareil. Les deux principaux éléments sont les suivants : 1. Location Manager : Fournit des points d'entrée vers les services de géolocalisation. 2. Location Providers : (fournisseurs de position) : Chacun d'eux représente une technologie de localisation de la position de l'appareil. A l'aide du Location Manager vous pouvez : 1. Obtenir une position courante. 2. Suivre des déplacements. 3. Déclencher des alertes de proximité en détectant les mouvements dans une zone spécifique. 4. Trouver les Location Providers disponibles. Lorsqu'il s'agit de déterminer la position courante de l'appareil, plusieurs problèmes peuvent être rencontrés : 1. Tous les appareils ne disposent pas tous du même matériel de géolocalisation (certains n'ont pas de récepteur GPS) ; 2. Les conditions d'utilisation du téléphone peuvent rendre inutilisable une méthode de localisation (cas de fonctionnalités GPS dans un tunnel, par exemple). Sélectionner un fournisseur de position - Location Provider En fonction de l'appareil, Android peut utiliser plusieurs technologies pour déterminer la position courante. Chacune d'elles, appelée Location Providers, offrira diverses caractéristiques en matière de consommation d'énergie, de coût, de précision et de capacité à déterminer l'altitude, la vitesse ou le cap. C'est pourquoi Android offre plusieurs moyens de localisation au travers d'une liste de fournisseurs de positions : selon les conditions du moment ou vos propres critères, Android se chargera de sélectionner le plus apte à donner la position de l'appareil. Il faudra donc récupérer cette liste de fournisseurs et déterminer celui qui est le plus adapté à l'utilisation souhaitée. De manière générale, nous distinguons deux types de fournisseurs naturels : 1. Le fournisseur basé sur la technologie GPS, de type LocationManager.GPS_PROVIDER. C'est le plus précis des deux, mais c'est également le plus consommateur en terme de batterie. 2. Le fournisseur qui se repère grâce aux antennes des opérateurs mobiles et aux points d'accès WI-FI, de type LocationManager.NETWORK_PROVIDER. // Ainsi, pour obtenir une instance d'un provider spécifique, appelez la méthode getprovider() en lui passant son nom : String fournisseur = LocationManager.GPS_PROVIDER; LocationProvider gpsprovider = syslocalisation.getprovider(fournisseur); Ceci n'est en général utile que pour déterminer les capacités d'un provider particulier. La plupart des méthodes d'un Location Manager ne requièrent que le nom du provider pour exécuter les services de géolocalisation. Obtenir la liste des fournisseurs de position Le but des services de géolocalisation est de déterminer la position de l'appareil. L'accès à ces services est géré par le système au travers de la méthode getsystemservice(). La classe des fournisseurs de position, LocationProvider, est une classe abstraite : chacune des différentes implémentations (correspondant aux différents moyens de localisation à notre disposition) fournit l'accès aux informations de localisation d'une manière qui lui est propre. // Le code suivant permet d'obtenir la liste de tous les fournisseurs : LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service);

11 List<String> fournisseurs = syslocalisation.getallproviders(); A un instant donné, tous les outils de géolocalisation de l'appareil ne sont peut-être pas disponibles : l'utilisateur peut en effet décider de désactiver certains moyens (par exemple, le GPS peut être désactivé pour économiser les batteries). Une autre alternative permet ainsi d'obtenir la liste de tous les providers disponibles sur l'appareil en appelant cette fois-ci la méthode getproviders() en spécifiant un booléen pour indiquer si vous désirez toute la liste des fournisseurs ou uniquement ceux qui sont activés : // Le code suivant permet d'obtenir la liste de tous les fournisseurs activés : LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service); boolean actifsseulement = true; List<String> fournisseurs = syslocalisation.getproviders(actifsseulement); Comme nous l'avons vu plus haut, si vous souhaitez obtenir un fournisseur particulier, appelez plutôt la méthode getprovider() en spécifiant le type de fournisseur à l'aide des constantes prédéfinies LocationManager.GPS_PROVIDER ou LocationManager.NETWORK_PROVIDER : LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service); String fournisseur = LocationManager.GPS_PROVIDER; LocationProvider gpsprovider = syslocalisation.getprovider(fournisseur); Comme la plupart des fonctionnalités sous Android, pour utiliser ces quelques lignes de code, il ne faut pas oublier de déclarer les permissions appropriées dans la section <manifest> du fichier de configuration de l'application. <manifest xmlns:android="http://schemas.android.com/apk/res/android"...> <application >... </application> // L'utilisation du fournisseur de type GPS est lié à la déclaration de permission suivante : <uses-permission android:name="android.permission.access_fine_location" /> // L'utilisation du fournisseur réseau dépend quant à lui de la permission : <uses-permission android:name="android.permission.access_coarse_location" /> </manifest> Nous retrouvons ici la notion de localisation grossière ou fine suivant la méthode utilisée par l'implémentation.. Je rappelle encore une fois que le but des services de géolocalisation est de déterminer la position de l'appareil. Une seule ligne de code suffit pour cela. Il faut cependant choisir auparavant un fournisseur de position. Une fois cette étape réalisée, nous utilisons la méthode getlastknownlocation() issue de la classe LocationManager, avec comme paramètre le fournisseur de position choisi. Le retour de cette méthode est une instance d'un objet Location si une position a été calculée, sinon son résultat vaut null : LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service); String fournisseur = LocationManager.GPS_PROVIDER; Location localisation = syslocalisation.getlastknownlocation(fournisseur); L'objet de type Location renvoyé par la méthode getlastknownlocation() inclut toutes les informations de position disponibles données par le fournisseur : latitude, longitude, cap, altitude, vitesse et heure à laquelle la position a été déterminée. Toutes ces propriétés sont accessibles via les méthodes getxxx() de l'objet Location. Dans certaines instances, des détails additionnels sont fournis dans le Bundle des compléments. Choix d'un fournisseur de position en fonctions de critères Il est peu probable dans la plupart des scenarii que vous souhaitiez explicitement choisir le fournisseur de position à utiliser. Plus communément, vous spécifierez les exigences qu'un fournisseur devra respecter et laisserez Android déterminer la meilleure technologie à utiliser. Une méthode existe dans Android pour choisir un fournisseur à partir de besoins que vous formulez, ce qui permet de déterminer le fournisseur le plus adapté à vos attentes. La classe centrale pour la définition des critères se nomme Criteria. Grâce à elle vous pouvez spécifier vos exigences en terme de précision (fine ou approximative), de consommation d'énergie (faible, moyenne, élevée), de coût et de capacité à renvoyer l'altitude, la vittesse et le cap. // Spécifie des critères de précision approximative, de faible consommation d'énergie et pas d'altitude, cap et vitesse. Criteria critères = new Criteria(); critères.setaccuracy(criteria.accuracy_coarse); critères.setpowerrequirement(criteria.power_low); critères.setaltituderequired(false); critères.setbearingrequired(false); critères.setspeedrequired(false); critères.setcostallowed(true); // Spécifie des critères de localisation la plus fine possible avec l'altitude fournie obligatoirement. Criteria critères = new Criteria(); critères.setaccuracy(criteria.accuracy_fine); critères.setaltituderequired(true); Une fois les critères définis, nous utilisons la méthode getbestprovider() de l'instance de la classe LocationManager récupérée un peu plus tôt. Le booléen en paramètre donne la possibilité de se restreindre aux fournisseurs activés uniquement.

12 LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service); Criteria critères = new Criteria(); critères.setaccuracy(criteria.accuracy_fine); critères.setaltituderequired(true); String fournisseur = syslocalisation.getbestprovider(critères, true); Si plus d'un fournisseur de position correspond à vos critères, celui possédant la précision la plus élevée est renvoyé. Si aucun fournisseur ne répond à vos exigences, les critères sont assouplis dans l'ordre suivant jusqu'à ce qu'un fournisseur soit trouvé : 1. Consommation d'énergie. 2. Précision. 3. Capacité à déterminer le cap, la vitesse et l'altitude. Le critère de coût n'est jamais modifié. Si aucun fournisseur n'est trouvé, la valeur null est renvoyée.. Avant d'aller plus loin, je vous propose de prendre tout de suite un premier exemple de géolocalisation qui permet de récupérer tout simplement la latitude et la longitude de votre mobile juste au démarrage de votre mobile. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="gps" > <activity android:name="geolocalisation" android:label="où suis-je?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.access_fine_location" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <TextView /> <TextView /> </LinearLayout> package fr.btsiris.localisation; import android.app.activity; import android.content.context; import android.location.*; import android.os.bundle; import android.widget.textview; public class Geolocalisation extends Activity { private TextView latitude, longitude; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); AndroidManifest.xml res/layout/main.xml fr.btsiris.localisation.geolocalisation.java LocationManager localisations = (LocationManager) getsystemservice(context.location_service); Location position = localisations.getlastknownlocation(locationmanager.gps_provider); if (position!=null) { latitude.settext("latitude : "+position.getlatitude()); longitude.settext("longitude : "+position.getlongitude()); else latitude.settext("position non déterminée ");

13 Si vous ne possédez pas de capteur GPS dans votre mobile, prévoyez le type de fournisseur LocationManager.NETWORK_PROVIDER. Je vous propose une autre alternative, qui aboutit au même résultat, qui prend en compte le choix du fournisseur en fonctions de différents critères. package fr.btsiris.localisation; fr.btsiris.localisation.geolocalisation.java import android.app.activity; import android.content.context; import android.location.*; import android.os.bundle; import android.widget.textview; public class Geolocalisation extends Activity { private TextView latitude, longitude; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); LocationManager localisations = (LocationManager) getsystemservice(context.location_service); Criteria critères = new Criteria(); critères.setaccuracy(criteria.accuracy_coarse); critères.setpowerrequirement(criteria.power_low); critères.setaltituderequired(false); critères.setbearingrequired(false); critères.setspeedrequired(false); critères.setcostallowed(true); String fournisseur = localisations.getbestprovider(critères, true); Location position = localisations.getlastknownlocation(fournisseur); if (position!=null) { latitude.settext("latitude : "+position.getlatitude()); longitude.settext("longitude : "+position.getlongitude()); else latitude.settext("position non déterminée "); Suivre les déplacements La plupart des applications de localisation doivent réagir aux déplacements de l'utilisateur. Se contenter de sonder le LocationManager ne suffira pas à forcer l'obtention des nouvelles mises à jour des Location Providers. Détecter le changement de position relève de deux problématiques : recevoir des mises à jour de sa position et détecter le mouvement. Ces deux problématiques de changement se résolvent par l'utilisation de la même méthode du LocationManager. Il s'agit de la méthode requestlocationupdates() dont voici les paramètres utiles : LocationManager syslocalisation = (LocationManager) getsystemservice(context.location_service); String fournisseur = LocationManager.GPS_PROVIDER; syslocalisation.requestlocationupdates(fournisseur, temps, distance, écouteurlocalisation); 1. Le premier paramètre est le fournisseur de position souhaité. 2. Ensuite, le deuxième paramètre indique le temps entre deux mises à jour exprimé en millisecondes. Attention à ne pas mettre de valeur trop faibles, qui accaparerait les ressources du téléphone et viderait votre batterie. La documentation du SDK recommande une valeur minimale de ms. 3. Le troisième paramètre précise la distance correspond au nombre de mètres qui doivent être parcourus avant de recevoir une nouvelle position. Si elle est supérieure à zéro, la mise à jour s'effectuera uniquement une fois la distance parcourue. 4. Enfin, le dernier paramètre est l'écouteur (une implémentation de l'interface LocationListener) qui recevra les diverses notifications. Cet écouteur propose quatre méthodes que vous devez redéfinir suivant les circonstances : class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { // Met à jour l'application en fonction de la nouvelle position. public void onstatuschanged(string fournisseur, int statut, Bundle extras) { // Met à jour l'application si le status du matériel a changé. public void onproviderenabled(string fournisseur) { // Met à jour l'application lorsque le fournisseur est activé. public void onproviderdisabled(string fournisseur) {

14 // Met à jour l'application lorsque le fournisseur est désactivé. 5. Pour arrêter les mises à jour, il faut fournir au gestionnaire de localisation l'instance de l'écouteur à retirer au travers de la méthode removeupdates(). Les GPS ont pour la plupart une consommation électrique significative. Pour la minimiser, vous devez désactiver les mises à jour à chaque fois que cela est possible, particulièrement lorsqu'elles servent à mettre à jour l'interface utilisateur mais que votre application n'est pas visible. Vous pouvez également améliorer les performances en augmentant le temps minimal entre les mises à jour. Dans ce deuxième exemple nous prévoyons une mise à jour de la position de votre mobile qui permet de suivre votre déplacement : package fr.btsiris.localisation; import android.app.activity; import android.content.context; import android.location.*; import android.os.bundle; import android.widget.*; public class Geolocalisation extends Activity { private TextView latitude, longitude; private LocationManager localisations; private String fournisseur; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); Criteria critères = new Criteria(); critères.setaccuracy(criteria.accuracy_coarse); critères.setpowerrequirement(criteria.power_low); critères.setaltituderequired(false); critères.setbearingrequired(false); critères.setspeedrequired(false); critères.setcostallowed(true); fr.btsiris.localisation.geolocalisation.java localisations = (LocationManager) getsystemservice(context.location_service); fournisseur = localisations.getbestprovider(critères, true); localisations.requestlocationupdates(fournisseur, 5000, 5, new ChangementPosition()); class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { if (position!=null) { latitude.settext("latitude : "+position.getlatitude()); longitude.settext("longitude : "+position.getlongitude()); else latitude.settext("position non déterminée "); Toast.makeText(Geolocalisation.this, "Nouvelle position", Toast.LENGTH_SHORT).show(); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { Voici une autre alternative qui prend en compte soit le GPS intégré soit le réseau téléphonique. Par ailleurs, je fais en sorte d'arrêter le service de géolocalisation lorsque l'activité n'est plus en action. Le service se remettra automatiquement en place lorsque nous réactiverons l'activité. package fr.btsiris.localisation; import android.app.activity; import android.content.context; import android.location.*; import android.os.bundle; import android.widget.*; public class Geolocalisation extends Activity { private TextView latitude, longitude; private LocationManager localisations; private String fournisseur; private ChangementPosition changements = new ChangementPosition(); fr.btsiris.localisation.geolocalisation.java

15 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); localisations = (LocationManager) getsystemservice(context.location_service); miseajourdelaposition(localisations.getlastknownlocation(locationmanager.gps_provider)); protected void onstart() { super.onstart(); localisations.requestlocationupdates(locationmanager.gps_provider, 2000, 2, changements); localisations.requestlocationupdates(locationmanager.network_provider, 5000, 5, changements); protected void onstop() { super.onstop(); localisations.removeupdates(changements); private void miseajourdelaposition(location position) { if (position!=null) { latitude.settext("latitude : "+position.getlatitude()); longitude.settext("longitude : "+position.getlongitude()); else latitude.settext("position non déterminée "); Toast.makeText(Geolocalisation.this, "Nouvelle position", Toast.LENGTH_SHORT).show(); class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { miseajourdelaposition(position); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { Alertes de proximité Il est souvent utile de voir vos applications réagir lorsque l'utilisateur se déplace en direction d'un lieu particulier ou s'en éloigne. Les alertes de proximité permettent de mettre en place des déclencheurs exécutés dans ces cas-là. Pour mettre en place une alerte de proximité pour une zone de donnée, sélectionnez le centre de celle-ci (en l'exprimant en longitude et latitude), le rayon et un délai d'expiration de l'alerte. Elle sera déclenchée si l'appareil entre ou sort du rayon défini. Lorsqu'elles sont déclenchées, les alertes de proximité exécutent des intentions, le plus souvent des Broadcast Intents. Pour spécifier l'intention, utilisez un PendingIntent, une classe qui wrappe l'intention en une sorte de pointeur de méthode au moyen de la méthode getbroadcast() : Intent intention = new Intent(MON_ACTION); PendingIntent proximité = PendingIntent.getBroadcast(this, -1, intention, 0); La méthode addproximityalert() de la classe LocationManager permet, quant à elle, d'enregistrer un PendingIntent qui sera déclenché quand l'appareil se trouvera à une certaine distance du point fixé. La méthode possède la signature suivante : public void addproximityalert(double latitude, double longitude, float rayon, long expiration, PendingIntent intention); 1. latitude : correspond à la latitude du point d'alerte ; 2. longitude : correspond à la longitude du point d'alerte ; 3. rayon : correspond au rayon autour du point d'alerte qui déterminera la sensibilité de la détection ; 4. expiration : définit une période en millisecondes à la fin de laquelle l'élément LocationManager retirera le point d'alerte des alertes surveillées. La valeur -1 signifie que l'enregistrement est valide jusqu'à qu'il soit retiré manuellement par la méthode removeproximityalert(). 5. intention : est utilisé pour générer l'intention déclenchée quand l'appareil rentre ou sort de la zone désignée par le point et le rayon. L'intention PendingIntent est étendue avec un extra dont la clé se nomme KEY_PROXIMITY_ENTERING. Nous obtenons alors un booléen dont la valeur est true si nous sommes dans la zone définie et false si nous la quittons. La gestion de l'alerte se réalise au travers d'un BroadcastReceiver, dont vous devez redéfinir la méthode de rappel onreceiver() qui permet de spécifier quelles actions vous souhaitez réaliser par rapport à la proximité du lieu, qu'il est ensuite nécessaire d'enregistrer au travers de la méthode registerreceiver(). Si l'activité n'est plus active, il est souhaitable d'enlever l'alerte au moyen de la méthode removeproximityalert(). Afin de factoriser toutes ces petites connaissances que nous venons d'acquérir, je vous propose de reprendre l'exemple précédent, et de l'étoffer avec une alerte qui se produit lorsque nous sortons d'une zone de 7 mètres de rayon. Quand l'activité démarre, elle mesure la postion actuelle

16 qui sert alors de référence pour l'alerte. package fr.btsiris.localisation; fr.btsiris.localisation.geolocalisation.java import android.app.*; import android.content.*; import android.location.*; import android.os.bundle; import android.widget.*; public class Geolocalisation extends Activity { private TextView latitude, longitude; private LocationManager localisations; private ChangementPosition changements = new ChangementPosition(); private static final String ALERTE_PROXIMITE = "fr.btsiris.localisation"; private PendingIntent proximite; private Location position; private boolean premierefois = true; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); localisations = (LocationManager) getsystemservice(context.location_service); protected void onstart() { super.onstart(); position = localisations.getlastknownlocation(locationmanager.gps_provider); localisations.requestlocationupdates(locationmanager.gps_provider, 3000, 2, changements); localisations.requestlocationupdates(locationmanager.network_provider, 3000, 2, changements); protected void onstop() { super.onstop(); localisations.removeupdates(changements); localisations.removeproximityalert(proximite); private void miseajourdelaposition(location position) { if (position!=null) { latitude.settext("latitude : "+position.getlatitude()); longitude.settext("longitude : "+position.getlongitude()); else latitude.settext("position non déterminée "); private void ajouteralerte() { Intent intention = new Intent(ALERTE_PROXIMITE); proximite = PendingIntent.getBroadcast(this, -1, intention, 0); double lat = position.getlatitude(); double lng = position.getlongitude(); localisations.addproximityalert(lat, lng, 7.0F, -1, proximite); IntentFilter filtre = new IntentFilter(ALERTE_PROXIMITE); registerreceiver(new Zone(), filtre); class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { if (premierefois) { premierefois = false; ajouteralerte(); miseajourdelaposition(position); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { class Zone extends BroadcastReceiver { public void onreceive(context context, Intent intention) { String cle = LocationManager.KEY_PROXIMITY_ENTERING; boolean dans = intention.getbooleanextra(cle, false); String alerte = dans? "Rentre dans la zone" : "Sort de la zone"; Toast.makeText(Geolocalisation.this, alerte, Toast.LENGTH_LONG).show();

17 Conversion d'adresses et d'endroits - Géocodage Le géocodage permet de déterminer des coordonnées en latitude et longitude à partir d'une adresse ou d'une description d'un endroit. A l'inverse, le géocodage permet de retrouver un lieu en fonction de ses coordonnées. Ces fonctionnalités sont liées à l'api Google. Ainsi, le géocodage, au travers de la classe Geocoder, permet de traduire automatiquement des adresses en coordonnées géographiques et inversement. Cela permet de connaître les positions utilisées par les services de géolocalisation et les activités géographiques. Les recherches de géocodage ont lieu sur le serveur et vos applications devront donc avoir la permission de se connecter à Internet : <manifest xmlns:android="http://schemas.android.com/apk/res/android"...> <application >... </application> // L'utilisation du fournisseur de type GPS est lié à la déclaration de permission suivante : <uses-permission android:name="android.permission.access_fine_location" /> // L'utilisation du géocodage dépend quant à lui de la permission : <uses-permission android:name="android.permission.internet" /> </manifest> La classe Geocoder donne accès à deux fonctions de géocodage : 1. Géocodage avant : Détermine la latitude et la longitude d'une adresse. 2. Géocodage inverse : Détermine l'adresse en fonction de la latitude et de la longitude. Le résultat de ces appels sont contextualisés par le biais d'une locale (utilisée pour définir votre emplacement et votre langue habituels). L'extrait qui suit montre comment la mettre en oeuvre lors de la création du Geocoder. Si vous ne la spécifiez pas, celle de votre appareil sera utilisée par défaut. Geocoder geocodage = new Geocoder(this, Locale.FRANCE); Geocoder geocodage = new Geocoder(this, Locale.getDefault()); Les deux fonctions de géocodage renvoient une liste d'objets Address. Chacun peut contenir plusieurs résultats en fonction d'une limite que vous spécifiez lors de l'appel. Chaque objet Address est renseigné avec tous les détails que le géocoder a pu déterminer. Cela peut inclure la latitude, la longitude, un numéro de téléphone et des détails d'adresse de granularité de plus en plus fine allant du pays au numéro de la voie. Les recherches du Geocoder sont effectuées de façon synchrone et bloquent donc le thread appelant. Dans le cas de connexions réseau lentes, cela peut se traduire par un écran figé. dans la plupart des cas, il est conseillé de déplacer ces recherches vers un Service ou un Thread en arrière-plan. Géocodage inverse Le géocodage inverse renvoie des adresses en fonctions de lieux spécifiés par leur latitude et leur longitude. Il permet de reconnaître les positions renvoyées par les services de géolocalisation. Pour effectuer une recherche inversée, passez la latitude et la longitude à la méthode getfromlocation() du Geocoder. Elle renverra une liste des correspondances possibles d'adresses. Si le Geocoder ne peut déterminer aucune adresse pour les coordonnées spécifiées, il renverra la valeur null. L'exactitude et la granularité des recherches inversées dépendent entièrement de la qualité des données de la base de géocodage. La conséquence en est que la qualité des résultats peut varier de façon importante selon les pays. Voici un exemple ci-dessous que permet de spécifier l'adresse complète de la localité où nous trouvons en plus des coordonnées géographiques : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="gps" > <activity android:name="geolocalisation" android:label="où suis-je?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.access_fine_location" /> <uses-permission android:name="android.permission.internet" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> AndroidManifest.xml res/layout/main.xml

18 <TextView /> <TextView /> <TextView /> </LinearLayout> class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { miseajourdelaposition(position); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { package fr.btsiris.localisation; fr.btsiris.localisation.geolocalisation.java import android.app.activity; import android.content.context; import android.location.*; import android.os.bundle; import android.widget.*; import java.io.ioexception; import java.util.*; public class Geolocalisation extends Activity { private TextView latitude, longitude, adresse; private LocationManager localisations; private ChangementPosition changements = new ChangementPosition(); private Geocoder geocodage; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); adresse = (TextView) findviewbyid(r.id.adresse); geocodage = new Geocoder(this, Locale.FRANCE); localisations = (LocationManager) getsystemservice(context.location_service); miseajourdelaposition(localisations.getlastknownlocation(locationmanager.gps_provider)); protected void onstart() { super.onstart(); localisations.requestlocationupdates(locationmanager.gps_provider, 2000, 2, changements); localisations.requestlocationupdates(locationmanager.network_provider, 5000, 5, changements); protected void onstop() { super.onstop(); localisations.removeupdates(changements); private void miseajourdelaposition(location position) { if (position!=null) { double lat = position.getlatitude(); double lng = position.getlongitude(); latitude.settext("latitude : "+lat); longitude.settext("longitude : "+lng); try { List<Address> adresses = geocodage.getfromlocation(lat, lng, 1); StringBuilder texte = new StringBuilder(); if (adresses.size() > 0) { Address une = adresses.get(0); texte.append(une.getaddressline(0)).append("\n"); texte.append(une.getpostalcode()).append(" ").append(une.getlocality()).append("\n"); texte.append(une.getcountryname()).append("\n"); adresse.settext(texte.tostring()); catch (IOException ex) { else latitude.settext("position non déterminée "); Toast.makeText(Geolocalisation.this, "Nouvelle position", Toast.LENGTH_SHORT).show();

19 Géocodage avant Le géocodage avant (ou tout simplement géocodage) détermine cette fois-ci les coordonnées géographique d'un lieu à partir d'un texte décrivant un endroit. Le format des adresses, noms de stations ou codes postaux dépend évidemment de la zone géographique concernée. Ce que nous appelons lieu valide varie effectivement en fonction de la zone géographique. Il s'agira en général d'une adresse normale de granularité variable (depuis le pays jusqu'au numéro dans la voie), d'un code postal, d'une gare, d'un monument, d'un hôpital ou, de façon générale, de tout ce qui peut être utilisé dans une recherche sur Google Maps. Pour effectuer un géocodage, appelez la méthode getfromlocationname() sur une instance de Geocoder. Passez-lui le lieu dont vous souhaitez obtenir les coordonnées ainsi que le nombre maximal de résultats : Geocoder geocodage = new Geocoder(this, Locale.FRANCE); List<Address> positions = geocodage.getfromlocationname(adresse, 1); La liste renvoyée peut inclure plusieurs correspondances possibles pour le lieu indiqué. Chaque résultat inclura une latitude et une longitude ainsi que toutes les informations d'adresse disponibles. Ceci est utile pour confirmer que la position a été correctement résolue ainsi que pour fournir des informations spécifiques lors de recherches de monuments. Comme pour le géocodage inverse, null est renvoyé si aucune correspondance n'est trouvée. De même, la disponibilité, l'exactitude et la granularité des résultats dépendent entièrement des données disponibles dans la base. Lorsque vous effectuez des recherches inversées, l'objet Locale spécifié lors de la création du Geocoder est particulièrement important. La Locale fournit le contexte géographique pour l'interprétation des résultats de recherche car un nom de lieu peut exister à plusieurs endroits. Lorsque c'est possible, sélectionnez une Locale régionale pour éviter les ambiguïtés. Voici un autre exemple qui permet de retrouver la latitude et la longitude à partir d'une adresse : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="localisation" > <activity android:name="localisation" android:label="latitude et Longitude?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.internet" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="3px"> <EditText android:hint="rue" /> <EditText android:hint="ville" /> <Button android:text="rechercher" android:onclick="localiser"/> <TextView android:textstyle="bold" android:textcolor="#00ff00" android:textsize="18sp" /> <TextView AndroidManifest.xml res/layout/main.xml

20 android:textstyle="bold" android:textcolor="#00ff00" android:textsize="18sp" /> </LinearLayout> fr.btsiris.localisation.localisation.java package fr.btsiris.localisation; import android.app.activity; import android.location.*; import android.os.bundle; import android.view.view; import android.widget.*; import java.io.ioexception; import java.util.*; public class Localisation extends Activity { private TextView latitude, longitude; private EditText rue, ville; private Geocoder geocodage; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (TextView) findviewbyid(r.id.latitude); longitude = (TextView) findviewbyid(r.id.longitude); rue = (EditText) findviewbyid(r.id.rue); ville = (EditText) findviewbyid(r.id.ville); geocodage = new Geocoder(this, Locale.FRANCE); public void localiser(view vue) { String adresse = rue.gettext().tostring() + ", " + ville.gettext().tostring() + ", France"; try { List<Address> positions = geocodage.getfromlocationname(adresse, 1); if (positions.size() > 0) { Toast.makeText(this, adresse, Toast.LENGTH_LONG).show(); Address position = positions.get(0); latitude.settext("latitude = "+position.getlatitude()); longitude.settext("longitude = "+position.getlongitude()); catch (IOException ex) { Toast.makeText(this, "Non localisée", Toast.LENGTH_LONG).show(); Création d'activités géographiques utilisant Google Maps La manipulation d'adresses ou de lieux, que ce soit pour un affichage ou une saisie, peut se faire via des affichages textes ou des formulaires mais l'utilisateur est habitué à tout visualiser sur des cartes. Afin de réaliser des applications utilisant ces vues à base de cartes, nous allons employer, entre autres, les différentes classes suivantes : 1. MapActivity : la classe de base à étendre pour créer une activité qui contiendra une carte (une MapView). Cette classe prend en charge le cycle de vie de l'application et la gestion du service d'arrière-plan requis pour afficher les cartes. En conséquence, vous ne pouvez utiliser les MapView que dans des activités dérivées de la classe MapActivity. 2. MapView : une vue affichant une carte sous forme de tuiles (parties de carte téléchargées indépendemment). L'une des façon les plus intuitive de présenter le contexte géographique est de l'afficher sur une carte. A l'aide d'une MapView, vous pouvez créer des activités présentant des cartes interactives. Les MapView supportent les annotations par le biais des Overlays et par l'épinglage de Views sur des positions géographiques. 3. MapControler : permet de contrôler la carte (centrage, niveau de zoom, etc.). Ce système apporte le contrôle total par programmation sur l'affichage de la carte, vous permettant de contrôler le zoom, la position et les modes d'affichage, y compris l'option d'afficher des vues satellite, de la rue ou du trafic. 4. Overlay : sorte de calque pour rajouter des annotations sur les cartes. Avec elle, vous pourrez utiliser un Canvas pour dessiner autant de couches que vous voudrez, qui seront affichées au dessus de la MapView. 5. MyLocationOverlay : est un Overlay particulier qui peut être utilisé pour afficher la position courante et l'orientation de l'appareil. 6. ItemizedOverlays et OverlayItems : sont utilisés pour vous permettre de créer une couche de marqueurs de cartes, affichés à l'aide de Drawable et de texte associé. Les activités géographiques sont exécutées nativement sur l'appareil, vous permettant de vous appyer sur son matériel et sa mobilité pour apporter une expérience personnalisée à l'utilisateur. Authentification - obtention de votre clé d'api Pour utiliser une MapView dans votre application, vous devrez d'abord obtenir une clé d'api sur le site des développeurs Android (voir plus loin). Sans elle, la MapView ne pourra pas télécharger les images utilisées pour

21 afficher la carte. Pour obtenir une clé, vous devrez spécifier l'empreinte MD5 du certificat utilisé pour signer votre application. En général, vous signerez votre application à l'aide de deux certificats, l'un de déboggage, l'autre de production. Pour voir les images des cartes pendant le débogage, vous devrez obtenir une clé d'api enregistrée via l'empreint MD5 du certificat de débogage. L'emplacement du magazin de clés (keystore) de débogage est stocké aux emplacement suivants : 1. Windows Vista & 7 : \Utilisateurs\<utilisateur>\.android\debug. 2. Windows XP : \Documents and Settings\<utilisateur>\.android\debug. 3. Linux ou Mac : ~/.android/debug.keystore. ATTENTION : Chaque ordinateur que vous utilisez lors du développement aura son propre certificat de débogage et sa propre valeur MD5. Si vous désirez déboguer et développer des applications géographiques sur plusieurs machines, vous devez générer et utiliser plusieurs clés d'api. Récupération de votre clé Google Map en mode débogage Comme nous venons de le signaler, afin de pouvoir utiliser Google Map dans votre application, il vous faut une clé API. Voici les étapes pour obtenir cette dernière : 1. Génération du MD5 : La première étape pour l obtention de la clé API, c est de créer votre MD5 Checksum. Il est crée grâce au debug certificate. Nous avons besoin de ce MD5, car il faut que la Google Map de l application soit signée et la clé API sert à ça. Pour créer votre MD5, il faut trouver où se situe votre fichier debug.keystore. Une fois le chemin connu, il faut taper la commande keytool en mode console et repecter les options suivantes : 2. Génération de votre clé API : Il suffit maintenant de vous rendre sur : Vous devez par contre disposer d un compte Google. Pour pouvoir générer votre clé, il vous suffit d accepter les termes et saisir le MD5 obtenu au dessus.

22 3. Obtention de la clé API : La clé obtenue correspond à l instance de votre ordinateur, si vous changer d ordinateur, il faudra générer une autre clé. Gardez la clé que vous avez obtenu dans un coin ou un fichier, nous verrons son utilisation lors de l'élaboration d'une application affichant une carte Google Map.

23 Récupération de votre clé Google Map en mode production définitive pour le déploiement sur votre smartphone Avant de compiler et de signer votre application pour la diffuser, vous devrez obtenir une clé d'api en utilisant l'empreinte MD5 de votre certificat de production. Déterminez-la avec la commande keytool et spécifiez le paramètre -liste et les keystore et alias que vous utiliserez pour signer votre application. Vous devrez entrer vos mots de passe de keystore et d'alias avant que l'empriente MD5 ne vous soit fournie. keytool -list -alias mon-android-alias -keystore mon-android-keystore Créer une activité géographique La classe MapActivity est étroitement liée à la classe MapView. Elle permet, grâce à ses différents méthodes, de prendre en charge les tâches - notamment de connexion réseau - pour simplifier l'utilisation d'une vue orientée carte. Pour utiliser des cartes dans vos applications, vous devez étendre MapActivity. Le layout de la nouvelle classe doit inclure une MapView pour afficher l'élément d'interface Google Maps. En pratique, l'emploi de la classe MapActivity dans une application nécessite plusieurs étapes : 1. La bibliothèque cartographique d'android n'est pas un paquetage standard Android. En tant qu'api optionnelle, elle doit être explicitement incluse dans le manifeste de l'application afin de pouvoit être utilisée. <uses-library android:name="com.google.android.maps" /> Ce paquetage ne fait pas partie du projet open-source Android standard. Il est fourni par Google dans le SDK Android et est disponible sur la plupart des appareils. Mais, n'étant pas standard, il se peut qu'un appareil ne le supporte pas. 2. Les images des cartes sont téléchargées à la demande et votre application doit donc avoir la permission d'utiliser Internet. Effectivement, puisque les tuiles du plan ainsi que toutes les informations comme le trafic routier sont téléchargées au fur et à mesure (vous devez avoir remarqué qu'à chaque déplacement de la carte, les zones sont affichées progressivement), il faut également prévoir la permission d'accéder aux données Internet comme suit : <uses-permission android:name="android.permission.internet" /> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="cartographie" > <activity android:name="cartographie" android:label="où suis-je?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> AndroidManifest.xml

24 <uses-library android:name="com.google.android.maps" /> </application> <uses-permission android:name="android.permission.internet" /> </manifest> 3. Une fois la bibliothèque ajoutée et la permission configurée, vous êtes prêt à créer votre nouvelle activité géographique. Cette activité contiendra une vue de type plan et un contrôleur de carte. Les vues de type plan (MapView ne peuvent être utiliés que dans une activité qui étend la classe MapActivity. Redéfinissez la méthode oncreate() pour disposer l'écran contenant la MapView et la méthode isroutedisplayed() pour envoyer true si l'activité doit afficher des informations de directions. Android ne supporte actuellement qu'une seule MapActivity et une seule MapView par application.. public class Cartographie extends MapActivity { private MapView carte; private MapController controleur; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); carte = (MapView) findviewbyid(r.id.carte); controleur = carte.getcontroller(); protected boolean isroutedisplayed() { return false; Manipuler la classe MapView La classe MapView gère l'affichage de la carte (proposée par Google). Par défaut, MapView montre la carte standard. Vous pouvez en plus choisir d'afficher une vue satellite, les vues plus précises des bâtiments au niveau de la rue ainsi que le trafic estimé : MapView carte = (MapView) findviewbyid(r.id.carte); carte.setsatellite(true); carte.setsteetview(true); carte.settraffic(true);; Vous pouvez également interroger la carte (MapView) pour déterminer les zooms courant et maximal ainsi que le centre de la carte et les longitudes et latitudes visibles (en degrés). Ce dernier (vois extrait ci-après) est particulièrement utile pour effectuer des recherches de géocodage géographiquement bornées. Vous pouvez également de façon optionnelle afficher des contrôles de zoom standard en utilisant la méthode setbuiltinzoomcontrols(). MapView carte = (MapView) findviewbyid(r.id.carte); int zoommaxi = carte.getmaxzoomlevel(); GeoPoint point = carte.getmapcenter(); int latitude = carte.getlatitudespan(); int longitude = carte.getlongitudespan(); carte.setbuiltinzoomcontrols(true); Pour employer correctement la classe MapView dans votre application, n'oubliez pas, bien entendu, de vous occuper de sa présentation au moyen du layout associé qui complète le squelette de l'activité que nous venons de découvrir : <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" >... <com.google.android.maps.mapview android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apikey="0kht1lzrcfazdgalqpfjthjqvo55qyxfq15lbza" /> </LinearLayout> res/layout/main.xml Nous retrouvons sans surprise la vue avec son identifiant. Nous constatons la présence d'un paramètre supplémentaire essentiel : la clé Google Maps. Elle peut soit être saisie directement dans la description de la vue, comme le montre le code précédent, soit donné sous forme de ressource annexe. Manipuler la classe MapControler

25 Nous utilisons la classe MapControler pour manipuler la carte en termes de position (recentrer) et de zoom. La récupération de l'instance de MapControler associée à une vue de type carte se fait à l'aide d'un accesseur getcontroller() de la classe MapView. MapController controleur = carte.getcontroller(); Dans les classes géographiques Android, les positions sur une carte sont représentées par des objets GeoPoint qui contiennent la latitude et la longitude mesurées en microdegrés, à savoir degrés*1e6 (10 puissance 6). Avant de pouvoir utiliser les valeurs de latitude et de longitude stockées dans les objets Location renvoyés par les services de géolocalisation, vous devez les convertir en microdegrés et les stocker en GeoPoint. Double lat = Double.parseDouble(latitude.getText().toString()) * 1E6; Double lng = Double.parseDouble(longitude.getText().toString()) * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intvalue()); controleur.animateto(point); ou controleur.setcenter(point); controleur.setzoom(1); 1. Recentrez et zoomez dans la carte en utilisant respectivement les setcenter() et setzoom() disponibles dans le MapController de la classe MapView. La méthode setcenter() permet de "sauter" vers une nouvelle position. Pour une transition sans à-coups, utilisez la méthode animateto() de MapView. 2. Lorsque vous utilisez la méthode setzoom(), 1 représente le zoom le plus large (le plus lointain) et 21, le plus proche. Le niveau de zoom réel disponible pour une position donnée dépend de la résolution des cartes Google et de l'imagerie disponible pour la zone. Vous pouvez également utiliser les méthodes zoomin() et zoomout() pour modifier le niveau par incrément de Lorsque vous utilisez Google Maps, vous remarquez que le fond cartographique se charge selon un découpage (dit tuilage) comprenant un ensemble de morceaux (tuiles) visibles à l'écran. Quand vous vous déplacez sur la carte, les tuiles sont chargées au fur et à mesure. Ce tuilage est stocké, pour chaque niveau de précision (et donc de zoom pour nous), sur les serveurs de Google. 4. Quand nous changeons le niveau de précision (de zoom), nous chargeons un ensemble de tuiles différent. Suivant la zone ciblée, les images sont disponibles à des précisions variées. Le niveau de zoom est réglable de 1 (plus petit niveau) à 21 inclus, sachant qu'il n'existe pas toujours de tuiles du lieu choisi aux niveaux de zoom les plus élevés (la zone n'a pas été photographiée avec cette précision ou elle fait partie d'un ensemble sensible qui doit être masqué par exemple). Pour donner un ordre d'idée, au niveau de zoom 1, l'equateur terrestre mesure 256 pixels de long et que chaque niveau de zoom successifs grossit d'un facteur 2. A titre d'exemple, je vous propose de faire une application simple, sans tenir compte pour l'instant de la géolocalisation, qui affiche la carte du lieu suivant la latitude et la longitude proposée manuellement avec visualisation des fonctions Zoom : <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="3px"> <EditText android:hint="latitude" /> <EditText android:hint="longitude" /> <Button android:text="localiser" android:onclick="localiser"/> <com.google.android.maps.mapview android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apikey="0kht1lzrcfazdgalqpfjthjqvo55qyxfq15lbza" /> </LinearLayout> res/layout/main.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="cartographie" > <activity android:name="cartographie" android:label="où suis-je?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-permission android:name="android.permission.internet" /> AndroidManifest.xml

26 </manifest> package fr.btsiris.localisation; fr.btsiris.localisation.cartographie.java import android.os.bundle; import android.view.view; import android.widget.*; import com.google.android.maps.*; public class Cartographie extends MapActivity { private EditText latitude, longitude; private MapView carte; private MapController controleur; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (EditText) findviewbyid(r.id.latitude); longitude = (EditText) findviewbyid(r.id.longitude); carte = (MapView) findviewbyid(r.id.carte); carte.setbuiltinzoomcontrols(true); controleur = carte.getcontroller(); protected boolean isroutedisplayed() { return false; public void localiser(view vue) { Double lat = Double.parseDouble(latitude.getText().toString()) * 1E6; Double lng = Double.parseDouble(longitude.getText().toString()) * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intvalue()); controleur.animateto(point); Placer des données sur une carte Les calques (Overlays) permettent d'ajouter des formes, des images ou du texte sur une vue de type carte (MapView). Il est possible de superposer plusieurs calques qui masquerons potentiellement des zones correspondant à des couches plus anciennes. Les Overlays vous permettent d'ajouter des annotations et une gestion des clics dans les MapViews. Chaque Overlay permet de tracer des primitives 2D, comme du texte, des lignes des images et des formes, sur un Canvas qui recouvrira ensuite une MapView. Vous pouvez ainsi ajouter plusieurs Overlays sur une carte. Tous les Overlays assignés à une MapView sont ajoutés comme des couches, chaque nouvelle couche masquant potentiellement les précédentes. Les clics des utilisateurs sont transmis au travers de la pile jusqu'à ce qu'ils soient pris en charge par un Overlay ou enregistrés comme des clics sur la MapView elle-même. Créer et utiliser des calques Pour créer un calque, vous devez définir une nouvelle classe qui hérite de la classe Overlay. Nous disposons alors d'une sorte de canevas pour dessiner. Les instructions de dessins personnalisés doivent alors être placées dans la méthode draw() qui est à redéfinir. Pour réagir aux actions utilisateur sur ces annotations, il faut également redéfinir la méthode ontap(). Chaque Overlay est un Canvas possédant un fond transparent, recouvrant une MapView et utilisé pour prendre en charge les clics sur la carte (qui ont lieu en général lorsque l'utilisateur tape sur une annotation ajoutée par l'overlay). import android.graphics.canvas; import com.google.android.maps.*; public class Calque extends Overlay { public void draw(canvas canevas, MapView carte, boolean ombres) { if (!ombres) { // Dessiner les annotations sur la couche principale else { // Dessiner les annotations sur la couche cachée public boolean ontap(geopoint centre, MapView carte) { return false;

27 Ajouter et supprimer des calques de la vue Chaque MapView contient la liste de ses calques en cours d'affichage. Nous pouvons obtenir une référence à cette liste en appelant la méthode getoverlays() issue de la classe MapView : List<Overlay> calques = carte.getoverlays(); Calque nouveau = new Calque(); calques.add(nouveau); carte.postinvalidate(); L'ajout et la suppression de calques dans la liste sont automatiquement sécurisés et synchronisés. L'ajout et la suppression se fait ainsi de façon totalement transparente, sans préoccupation de gestion multi-tâche étant donné que la liste a été passé à la méthode Collection.synchronized() avant de nous être retournée. Ainsi, pour ajouter un Overlay sur une MapView, créez simplement une instance de l'overlay et ajoutez-la à la liste sans autre écriture supplémentaire. Tous les calques de la liste seront dessinés par odre croissant et recevront des événements progressivement en ordre inverse tant qu'un calque ne les traitera pas. L'Overlay ajouté sera affiché la prochaine fois que la MapView sera redessinnée et il est donc conseillé d'appeler la méthode postinvalidate() après une modification de la liste afin de mettre à jour l'affichage de la carte. Dessiner sur un calque Le dessin sur un calque est possible en redéfinissant la méthode draw() du calque, un peu comme si nous redéfinissions la méthode paintcomponent() d'un composant Swing de l'api de Java SE. Comme nous l'avons découvert, cette méthode draw() possède trois paramètres que nous allons maintenant détailler : 1. Le premier est de type Canvas et constitue la surface sur laquelle nous allons dessiner. L'objet Canvas contient des méthodes pour tracer des primitives 2D sur vos cartes (lignes, texte, formes, ellipses, images, etc.). Utilisez des objets Paint pour définir le style et la couleur. 2. Le deuxième est la référence de type MapView qui correspond à la vue qui affiche la carte concernée par notre dessin. 3. Le dernier est un booléen qui indique dans quel appel nous nous trouvons. En effet, cette méthode est appelée deux fois pour permettre, quand ombres vaut true, de dessiner des ombres aux formes créées. ATTENTION : Le Canvas utilisé pour tracer les annotations des Overlays est un Canvas standard représentant la surface affichée visible. Pour ajouter des annotations basées sur des positions, vous devrez effectuer une conversion entre coordonnées géographiques et coordonnées d'écran. Introduction aux projections La classe Projection vous permet de traduire des coordonnées de latitude et longitude en coordonnées d'écran. Un objet de type Projection est obtenu grâce à la méthode getprojection() de la classe MapView. Dès lors, à l'aide de cet objet, nous pouvons réaliser la conversion dans les deux sens, d'un GeoPoint vers un Point et vice versa, respectivement avec les méthodes topixels() et frompixels(). Projection transformation = carte.getprojection(); Double lat = Double.parseDouble(latitude.getText().toString()) * 1E6; Double lng = Double.parseDouble(longitude.getText().toString()) * 1E6; GeoPoint pointcarte = new GeoPoint(lat.intValue(), lng.intvalue()); Point pointecran = new Point(); transformation.topixels(pointcarte, pointecran); Les styles Nous disposons à présent de coordonnées pour dessiner. Il reste à choisir le style du pinceau que nous allons utiliser pour réaliser une forme, et c'est là qu'intervient la classe Paint. Elle va nous permettre de déterminer le style et la couleur des formes et des textes que nous voulons dessiner sur le calque grâce aux méthodes setargb(), setalpha(), setcolor(), setantialias(), etc. Paint pinceau = new Paint(); pinceau.setargb(255, 255, 255, 0); La boîte à outils du dessinateur Une fois les coordonnées de la forme et le style choisi, nous utilisons toutes les méthodes de l'instance de Canvas pour réaliser des tracés personnalisés 2D sur vos cartes (lignes, texte, formes, ellipse, images, etc.) à l'aide des méthodes tel que drawline(), drawcircle(), drawrect(), drawtext(), drawbitmap(), drawpicture(), etc. canevas.drawtext("c'est ici!", pointecran.x, pointecran.y, pinceau); Réagir à une sélection Le tapotement sur l'écran sont l'équivalent des clics de souris. Vous les prendrez en charge en redéfinissant le gestionnaire ontap() dans la classe d'extension de l'overlay. Il reçoit deux paramètres : 1. Un GeoPoint contenant la latitude et la longitude de la position cliquée. 2. La MapView sur laquelle l'événement a été déclenché. Lorsque vous décidez de redéfinir ontap(), la méthode devra renvoyer true si elle a pris en charge un clic particulier et false pour laisser un autre Overlay le gérer. Nous modifions le projet précédent pour intégrer un calque qui affiche une simple annotation qui montre un point bleu et le texte "Je suis là"

28 également en bleu systématiquement au centre de la carte : package fr.btsiris.localisation; fr.btsiris.localisation.calque.java import android.graphics.*; import com.google.android.maps.*; public class Calque extends Overlay { public void draw(canvas canevas, MapView carte, boolean ombres) { if (!ombres) { Point pointecran = new Point(); Projection transformation = carte.getprojection(); transformation.topixels(carte.getmapcenter(), pointecran); Paint pinceau = new Paint(); pinceau.setargb(200, 0, 0, 255); pinceau.setantialias(true); pinceau.setfakeboldtext(true); int rayon = 5; canevas.drawcircle(pointecran.x, pointecran.y, rayon, pinceau); canevas.drawtext("je suis là", pointecran.x+10, pointecran.y-10, pinceau); public boolean ontap(geopoint centre, MapView carte) { return false; package fr.btsiris.localisation; fr.btsiris.localisation.cartographie.java import android.os.bundle; import android.view.view; import android.widget.*; import com.google.android.maps.*; import java.util.list; public class Cartographie extends MapActivity { private EditText latitude, longitude; private MapView carte; private MapController controleur; private Calque calque = new Calque(); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); latitude = (EditText) findviewbyid(r.id.latitude); longitude = (EditText) findviewbyid(r.id.longitude); carte = (MapView) findviewbyid(r.id.carte); carte.setbuiltinzoomcontrols(true); controleur = carte.getcontroller(); List<Overlay> calques = carte.getoverlays(); calques.add(calque); protected boolean isroutedisplayed() { return false; public void localiser(view vue) { Double lat = Double.parseDouble(latitude.getText().toString()) * 1E6; Double lng = Double.parseDouble(longitude.getText().toString()) * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intvalue()); controleur.animateto(point); Calque MyLocationOverlay L'API Android propose également un calque prédéfini spécifique qui peut répondre à deux problématiques usuelles : 1. Montrer où vous êtes sur la carte à partir de coordonnées GPS ou d'un autre fournisseur de position. 2. Montrer votre orientation grâce à la boussole électronique si elle est disponible sur votre matériel. La classe MyLocationOverlay est un Overlay particulier pour afficher votre position courante et votre orientation sur une MapView. Pour

29 l'utiliser, vous devez l'intancier, lui passer le contexte de l'application et la carte cible puis l'ajouter à la liste des Overlays. MapView carte = (MapView) findviewbyid(r.id.carte); List<Overlay> calques = carte.getoverlays(); MyLocationOverlay calque = new MyLocationOverlay(this, carte); calques.add(calque); calque.enablecompass(); calque.enablemylocation(); Vous pouvez maintenant l'utiliser pour afficher à la fois votre position courante (représentée par un marqueur bleu clignotant) et votre direction (représentée sous la forme d'une boussole sur la carte). Pour activer ces fonctionnalités spécifiques, utilisez pour cela les méthodes enabledcompass() et enablemylocation() sur l'instance que vous souhaitez impacter. Afin d'optimiser la durée de vie de la batterie, il serait judicieux d'activer et de désactiver ces fonctionnalités au moment opportun, au travers des méthodes de rappel, respectivement onresume() ou onstart() et onpause() ou onstop(). Je vous propose encore un autre exemple qui fusionne cette fois-ci la géolocalisation issue du GPS et qui visualise cette localisation à l'aide de Google Map en affichant en plus la boussole et le point clignotant représentant le position trouvée. (La capture d'écran, issue de l'émulateur, ne montre malheureusement pas ce calque particulier). <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.localisation" android:versioncode="1" android:versionname="1.0"> <application android:label="cartographie"> <activity android:name="cartographie" android:label="où suis-je?"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <uses-library android:name="com.google.android.maps" /> </application> <uses-permission android:name="android.permission.internet" /> <uses-permission android:name="android.permission.access_fine_location" /> </manifest> <com.google.android.maps.mapview xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apikey="0kht1lzrcfaxp_vuzbfx3sczmpytt-8wmsdx2ra" /> package fr.btsiris.localisation; import android.content.context; import android.location.*; import android.os.bundle; import com.google.android.maps.*; import java.util.list; public class Cartographie extends MapActivity { private MapView carte; private MapController controleur; private MyLocationOverlay calque; private LocationManager localisations; private ChangementPosition changements = new ChangementPosition(); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); carte = (MapView) findviewbyid(r.id.carte); carte.setbuiltinzoomcontrols(true); controleur = carte.getcontroller(); List<Overlay> calques = carte.getoverlays(); calque = new MyLocationOverlay(this, carte); calques.add(calque); localisations = (LocationManager) getsystemservice(context.location_service); miseajourdelaposition(localisations.getlastknownlocation(locationmanager.gps_provider)); AndroidManifest.xml res/layout/main.xml fr.btsiris.localisation.cartographie.java

30 protected boolean isroutedisplayed() { return false; protected void onstart() { super.onstart(); localisations.requestlocationupdates(locationmanager.gps_provider, 2000, 2, changements); localisations.requestlocationupdates(locationmanager.network_provider, 5000, 5, changements); calque.enablecompass(); calque.enablemylocation(); protected void onstop() { super.onstop(); localisations.removeupdates(changements); calque.disablecompass(); calque.disablemylocation(); private void miseajourdelaposition(location position) { if (position!=null) { Double lat = position.getlatitude() * 1E6; Double lng = position.getlongitude() * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intvalue()); controleur.animateto(point); class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { miseajourdelaposition(position); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { Classes ItemizedOverlay et OverlayItem Comme son nom l'indique, la classe ItemizedOverlay affiche sur une carte une liste de points d'intérêts. Ces points sont des instances de la classe OverlayItem. La classe ItemizedOverlay fourni un raccourci pour ajouter des marqueurs à une carte, vous permettant d'assigner une image et son texte associé à une position géographique. L'instance ItemizedOverlay prend en charge le dessin, le placement, la gestion des clics, le contrôle du focus et l'optimisation du layout de chaque OverlayItem. Pour ajouter un marqueur ItemizedOverlay à votre carte, il est nécessaire de créer une nouvelle classe étendant ItemizedOverlay<OverlayItem>. En réalité, ItemizedOverlay est une classe générique qui vous permet de créer des extensions fondées sur n'importe quelle sous-classe dérivée d'overlayitem. Voici les différentes étapes de la manipulation de ce type de calque : 1. Comme nous venons de le dire, en premier lieu, une classe doit hériter de Itemized<OverlayItem>. Nous créons également un constructeur qui prendra en paramètre un objet de type Drawable. Cet objet est un marqueur qui mettra en valeur les points que nous souhaitons montrer (comme les bulles rouges de Google Maps). 2. Dans le constructeur, préparez tous les éléments OverlayItem (la localisation, un titre, un texte plus complet) et appeler la méthode populate() une fois qu'ils sont prêts. 3. Implémenter la méthode size() qui renvoie le nombre d'éléments du calque. 4. Redéfinir la méthode createitem() pour renvoyer les éléments OverlayItem un par un en fonction de l'index donné. 5. Instancier la classe ItemizedOverlay et l'ajouter aux calques de la vue. Afin de bien comprendre ces différentes notions, je vous propose de rajouter des fonctionnalités supplémentaires sur l'application précédente. Nous pouvons placer un certain nombre de marqueurs, visualisés sous forme de pastilles, qui seront créés à partir de la saisie d'une adresse. Une fois que le marqueur est placé sur la carte, il est possible de visualiser l'adresse exacte par une simple clic sur la pastille représentant ce marqueur.

31 Dans cette application, nous disposons maintenant de deux vues, donc de deux activités. Il est donc nécessaire de prévoir deux classes séparées avec leurs layouts associés. La gestion des marqueurs se fera également dans une classe à part. Pour générer ces pastilles qui poitent les différents lieux sur la carte, nous avons besoin d'une simple image créée avec n'importe quel éditeur graphique ou téléchargée sur Internet. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:padding="2px"> <Button android:text="nouvelle position" android:layout_alignparentbottom="true" android:onclick="localiser"/> <com.google.android.maps.mapview android:layout_height="fill_parent" android:enabled="true" android:clickable="true" android:apikey="0kht1lzrcfaxp_vuzbfx3sczmpytt-8wmsdx2ra" /> </RelativeLayout> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="2px"> <EditText android:hint="rue" /> <EditText android:hint="ville" /> <Button android:text="rechercher" android:onclick="valider"/> </LinearLayout> res/layout/main.xml res/layout/adresses.xml fr.btsiris.localisation.cartographie.java

32 package fr.btsiris.localisation; import android.content.*; import android.graphics.drawable.drawable; import android.location.*; import android.os.bundle; import android.view.view; import android.widget.toast; import com.google.android.maps.*; import java.io.ioexception; import java.util.*; public class Cartographie extends MapActivity { private MapView carte; private MapController controleur; private MyLocationOverlay position; private Marqueur marqueurs; private LocationManager localisations; private ChangementPosition changements = new ChangementPosition(); private Intent intention; private Geocoder geocodage; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); intention = new Intent(this, NouvelleAdresse.class); carte = (MapView) findviewbyid(r.id.carte); carte.setbuiltinzoomcontrols(true); controleur = carte.getcontroller(); List<Overlay> calques = carte.getoverlays(); position = new MyLocationOverlay(this, carte); calques.add(position); Drawable pastille = getresources().getdrawable(r.drawable.marqueur); marqueurs = new Marqueur(pastille, this); calques.add(marqueurs); geocodage = new Geocoder(this, Locale.FRANCE); localisations = (LocationManager) getsystemservice(context.location_service); miseajourdelaposition(localisations.getlastknownlocation(locationmanager.gps_provider)); protected boolean isroutedisplayed() { return false; protected void onstart() { super.onstart(); localisations.requestlocationupdates(locationmanager.gps_provider, 2000, 2, changements); localisations.requestlocationupdates(locationmanager.network_provider, 5000, 5, changements); position.enablecompass(); position.enablemylocation(); protected void onstop() { super.onstop(); localisations.removeupdates(changements); position.disablecompass(); position.disablemylocation(); private void miseajourdelaposition(location position) { if (position!=null) { Double lat = position.getlatitude() * 1E6; Double lng = position.getlongitude() * 1E6; GeoPoint point = new GeoPoint(lat.intValue(), lng.intvalue()); controleur.animateto(point); class ChangementPosition implements LocationListener { public void onlocationchanged(location position) { miseajourdelaposition(position); public void onstatuschanged(string fournisseur, int statut, Bundle extras) { public void onproviderenabled(string fournisseur) { public void onproviderdisabled(string fournisseur) { public void localiser(view bouton) { startactivityforresult(intention, 1); protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (resultcode==result_ok) {

33 String endroit = data.getextras().getstring("localisation"); try { List<Address> positions = geocodage.getfromlocationname(endroit, 1); if (positions.size() > 0) { Address localisation = positions.get(0); marqueurs.ajout(localisation); catch (IOException ex) { Toast.makeText(this, "Non localisée", Toast.LENGTH_LONG).show(); 1. La classe représentant le calque de gestion des différents marqueurs se nomme Marqueur dont le code est présenté plus bas. Il s'agit finalement d'un calque comme un autre qui est rajouté à l'ensemble des calques. 2. L'objet pastille récupère l'image proposée dans les ressources. package fr.btsiris.localisation; fr.btsiris.localisation.nouvelleadresse.java import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.edittext; public class NouvelleAdresse extends Activity { private EditText rue, ville; public void oncreate(bundle icicle) { super.oncreate(icicle); setcontentview(r.layout.adresses); rue = (EditText) findviewbyid(r.id.rue); ville = (EditText) findviewbyid(r.id.ville); public void valider(view vue) { String adresse = rue.gettext().tostring() + ", " + ville.gettext().tostring() + ", France"; getintent().putextra("localisation", adresse); setresult(result_ok, getintent()); finish(); package fr.btsiris.localisation; fr.btsiris.localisation.marqueur.java import android.content.context; import android.graphics.canvas; import android.graphics.drawable.drawable; import android.location.address; import android.widget.toast; import com.google.android.maps.*; import java.util.arraylist; public class Marqueur extends ItemizedOverlay<OverlayItem> { private ArrayList<OverlayItem> liste = new ArrayList<OverlayItem>(); private Drawable image; private Context contexte; public Marqueur(Drawable image, Context contexte) { super(image); this.image = image; this.contexte = contexte; populate(); protected OverlayItem createitem(int index) { return liste.get(index); public int size() { return liste.size(); public void ajout(address adresse) { Double latitude = adresse.getlatitude() * 1E6; Double longitude = adresse.getlongitude() * 1E6; GeoPoint point = new GeoPoint(latitude.intValue(), longitude.intvalue()); liste.add(new OverlayItem(point, adresse.getlocality(), adresse.getaddressline(0)));

34 populate(); public void draw(canvas canevas, MapView carte, boolean ombre) { super.draw(canevas, carte, ombre); boundcenterbottom(image); protected boolean ontap(int index) { OverlayItem marque = liste.get(index); String texte = marque.gettitle() +'\n'+ marque.getsnippet() + '\n'+ marque.routableaddress(); Toast.makeText(contexte, texte, Toast.LENGTH_LONG).show(); return true; Vous avez juste ci-dessus le code du calque à proprement parler. Il est chargé d'afficher les différents lieux importants. Si l'uilisateur sélectionne l'un des marqueurs, un descriptif s'affichera. 1. En suivant la démarche proposée, nous devons d'abord utiliser le mécanisme d'héritage. 2. Ensuite, nous créons le constructeur qui prend en paramètre le marqueur (stocké pour être réutilisé) et le contexte (également stocké, notamment pour l'affichage de messages après sélection des éléments). 3. Nous créons une collection d'overlayitem qui va contenir tous les marqueurs à afficher avec une pastille sur la carte. 4. Avec la méthode ajout(), nous ajoutons un par un les marqueurs à cette collection en précisant, à chaque fois, la localisation exacte, le nom de la ville suivi de l'adresse complète. 5. Parallèlement, nous redéfinissons les méthodes obligatoires createitem() et size(). La première renvoie un marqueur situé à un index donné dans la collection, la seconde donne la taille de la collection. Ces deux méthodes permettent au calque de parcourir les marqueurs et de les afficher avec l'appel de la méthode populate(). 6. En redéfinissant la méthode draw(), nous délégons une grosse partie du travail de dessin du marqueur au calque, mais nous l'aidons tout de même à déterminer l'emplacement de la pastille et surtout le bas afin que l'ombre soit correctement dessinée. 7. L'affichage des informations complémentaires sur chaque pastille est réalisé via la méthode ontap(). Nous affichons sous forme de toast le marqueur concerné en rapprochant l'index passé en paramètre de l'élément de rang équivalent dans la collection globale. Epingler des Views à une carte et à des positions Vous avez la possibilité d'épingler n'importe quel objet dérivé d'une View à une MapView (y compris les layouts et autres ViewGroup) en l'attachant à une position à l'écran ou géographique. Dans le dernier cas, la view se déplacera pour suivre sa position épinglée sur la carte, agissant comme un véritable marqueur interactif. Cette solution consommant plus de ressources, elle est habituellement réservée à l'affichage de détails dans une bulle affichée pour fournir des informations complémentaires lorsque nous cliquons sur un marqueur. 1. Vous pouvez implémenter les deux mécanismes en appelant addview() sur la carte, en général depuis les méthodes oncreate() ou onrestore() dans l'activité la contenant. Passer la vue que vous désirez épingler ainsi que les paramètres de layout à utiliser. 2. Les paramètres MapView.LayoutParams que vous passerez à la méthode addview() déterminent comment la vue est ajoutée à la carte. 3. Pour ajouter une nouvelle View relativement à l'écran, spécifiez un nouveau MapView.LayoutParams contenant les arguments correspondant à la hauteur et à la largeur de la View, les coordonnées cibles (x, y) de l'écran et l'alignement à utiliser pour le positionnement. 4. Pour épingler une View relativement à une position géographique, passez quatre paramètres dans MapView.LayoutParams, représentant la hauteur, la largeur, le GeoPoint cible et l'alignement du layout. 5. Un panoramique de la carte laissera la première TextView en place dans le coin supérieur gauche alors que la seconde se déplacera pour rester épinglée à sa position. 6. Pour supprimer une View d'une MapView, appelez la méthode removeview() en passant l'instance de View que vous voulez supprimer. Capteurs, accéléromètre (Dessins 2D - Drawables) Les dernières générations de téléphones tentent d'embarquer de plus en plus de fonctionnalités matérielles : GPS, boussole, gyroscope, Wi-Fi, Bluetooth, capteur magnétique, capteur de température, etc. Bien évidemment, en tant que dévelopeur, vous comprendrez immédiatement que ce sont autant d'opportunités de créer des applications toujours plus immersives, communicantes et interactives! Nous allons découvrir dans ce chapitre les capteurs existant sous Android et comment utiliser le Sensor Manager pour les monitorer. Nous examinerons de près l'accéléromètre et les capteurs d'orientation et les utiliserons pour déterminer les changements dans l'orientation de l'appareil ainsi que l'accélération. Ceci sera particulièrement utile pour créer des interfaces utilisateur basées sur le mouvement et vous permettra de donnéer une nouvelle dimension à vos applications basées sur la géolocalisation. Les ressources disponibles sur un appareil Android La plate-forme Android gère de façon native bon nombre de ressources matérielles telles que le clavier et le trackball qui ne nécessitent pas vraiment d'adaptation lorsque ceux-ci ne sont pas disponibles sur le terminal. Par exemple, le clavier physique sera remplacé par un clavier virtuel sans avoir besoin de reprogrammer l'interface utilisateur.

35 Liste des principales ressources utilisables depuis Android Affichage - Appareil photo - 3D Entrées - Clavier - Matrice tactile - Boule de commande (trackball) Son - Haut parleur / Microphone - Reconnaissance vocale - Vibration Réseau - 3G - Wi-Fi - Bluetooth - GPS Capteurs - Accéléromètre - Orientation - Champ magnétique - Température Les capteurs Une bonne partie du succès des terminaux modernes est la gestion du mouvement. Cela ne serait pas possible sans l'extraordinaire démocratisation des capteurs embarqués. Désormais, votre appareil détecte le mouvement et peut se situer dans l'espace. Voyons comment tout cela fonctionne sur Android, en créant un projet qui utilise les capteurs. Comme pour les services de géolocalisation, Android rend abstraites les implémentations des capteurs de chaque appareil. La classe Sensor est utilisée pour décrire les propriétés de chaque capteur matériel, comme son type, son nom, son fabricant et ses détails de précision et de portée. Identification des capteurs Le panel des capteurs s'est étoffé avec les versions successives d'android : accéléromètre, capteur de pression, capteur de proximité... Ceci dit, il ne faut pas croire que tous les capteurs sont disponibles sur tous les terminaux, loin de là! L'API d'android 1.6 a donc introduit une nouvelle classe Sensor responsable de la récupération des données, sous la houlette d'un autre classe nommée SensorManager qui comme son nom l'indique chapeaute le groupe de capteurs au travers d'un service commun à tout le système. SensorManager est ainsi utilisé pour gérer l'ensemble des capteurs disponibles sur les appareils Android. Utilisez la méthode getsystemservice() pour renvoyer une référence vers le service SensorManager : SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); Ensuite, la classe Sensor contient un ensemble de constantes utilisées pour décrire quel type de capteur matériel est représenté par un objet de type Sensor. Ces constantes sont de la forme Sensor.TYPE_<Type>. La section suivante décrit chaque type de capteur supporté. Vous apprendrez ensuite comment trouver et utiliser chacun d'eux. Sensor.TYPE_ACCELEROMETER Liste des différents types de capteurs disponibles Permet d'évaluer les mouvements du terminal ou tout simplement la gravité. Accéléromètre à trois axes renvoyant l'accélération courante en m/s 2. Sensor.TYPE_GYROSCOPE Permet de connaître la position angulaire en degré du terminal en fonction des trois axes x, y et z. Sensor.TYPE_LIGHT Le capteur de lumière permet d'évaluer l'exposition lumineuse subie par le terminal. Capteur de lumière ambiante renvoyant une valeur exprimée en lux. Le capteur de lumière est couramment utilisé pour contrôler la luminosité de l'écran. Sensor.TYPE_MAGNETIC_FIELD Permet de détecter les modifications des champs magnétiques environnants. Capteur de champ magnétique renvoyant le champ en microteslas suivant les trois axes. Sensor.TYPE_ORIENTATION Permet de déterminer la position du terminal en fonction des trois axes x, y et z. Sensor.TYPE_PRESSURE Indique la pression atmosphérique. Capteur de pression renvoyant une valeur exprimée en kilopascals. Sensor.TYPE_PROXIMITY Indique la distance du terminal par rapport à un objet. Capteur de proximité indiquant la distance entre l'appareil et un objet cible en mètres. La façon dont l'objet est sélectionné et la distance supportée dépendront de l'implémentation matérielle du détecteur de proximité. Un usage typique est la détection de l'approche de l'appareil de l'oreille de l'utilisateur, l'ajustement automatique de la luminosité de

36 l'écran ou le lancement d'une commande vocale. Sensor.TYPE_TEMPERATURE Indique la température à proximité du capteur. Thermomètre renvoyant la température en degré Celsius. Il peut s'agir de la température ambiante, de celle de la batterie ou d'un capteur distant en fonction de l'implémentation matérielle. Trouver les capteurs Un appareil Android peut contenir plusieurs implémentations d'un type de capteur particulier. Pour déterminer l'implémentation par défaut, utilisez la méthode getdefaultsensor() issue de la classe SensorManager en lui passant le type de capteur requis sous la forme d'une des constantes décrites dans la section précédente. Si le capteur n'est pas disponible sur le terminal, la méthode getdefaultsensor() renverra une valeur null nous permettant d'agir en conséquence. SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); Sensor gyroscope = gestioncapteurs.getdefaultsensor(sensor.type_gyroscope); De façon alternative, utilisez plutôt la méthode getsensorlist() pour renvoyer la liste de tous les capteurs d'un type donné comme dans le code suivant, qui renvoie tous les capteurs de pression : SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); List<Sensor> capteurspression = gestioncapteurs.getsensorlist(sensor.type_pressure); Enfin, pour déterminer tous les capteurs disponibles sur une plate-forme hôte, utilisez de nouveau la méthode getsensorlist(), en lui passant cette fois-ci la constante Sensor.TYPE_ALL. Cette technique vous permet de déterminer quels capteurs et types de capteurs sont disponibles sur l'appareil. SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); List<Sensor> capteurs = gestioncapteurs.getsensorlist(sensor.type_all); Je vous propose de réaliser un premier projet qui permet de recenser tous les capteurs que vous avez à votre disposition sur votre terminal. <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" /> package fr.btsiris.capteurs; import android.app.listactivity; import android.content.context; import android.hardware.*; import android.os.bundle; import android.widget.arrayadapter; import java.util.list; public class Capteur extends ListActivity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); List<Sensor> capteurs = gestioncapteurs.getsensorlist(sensor.type_all); String[] listecapteurs = new String[capteurs.size()]; for (int i=0; i<capteurs.size(); i++) listecapteurs[i] = capteurs.get(i).getname(); setlistadapter(new ArrayAdapter<String>(this, android.r.layout.simple_list_item_1, listecapteurs)); res/layout/main.xml fr.btsiris.capteurs.capteur.java Comment utiliser les capteurs Pour accéder aux capteurs, après avoir récupérer l'instance du service, vous devez vous abonner aux événements du capteur qui vous intéressent. Pour cela, vous devez implémenter l'interface SensorEventListener et redéfinir les méthodes adpatées à ce type d'événements, d'une part la méthode onsensorchanged() pour monitorer les valeurs du capteur et d'autre part la méthode onaccuracychanged() pour réagir à ses changements de précision. class Scrutation implements SensorEventListener { public void onsensorchanged(sensorevent evt) { // A faire : Monitorer les changements dans le capteur

37 public void onaccuracychanged(sensor capteur, int accuracy) { // A faire : Réagir aux demandes de changement de précision du capteur Le paramètre de type SensorEvent dans la méthode onsensorchanged() contient quatre propriétés utilisées pour décrire un événement particulier du capteur. Paramètres de SensorEvent sensor L'objet de type Sensor ayant déclanché l'événement. accuracy La précision du capteur lorsque l'événement est survenu (faible, moyenne, haute ou non fiable). values Tableau de flottants contenant la ou les nouvelles valeurs détectées. timestamp L'horodatage (en nanosecondes) auquel l'événement est survenu. Vous pouvez monitorer séparément les changements dans la précision d'un capteur en utilisant la méthode onaccuracychanged(). Dans les deux gestionnaires, la valeur accuracy représente la précision du capteur monitoré sous la forme de constantes prédéfinies dont la liste est proposée ci-dessous. Précision du capteur SensorManager.SENSOR_STATUS_ACCURACY_LOW Indique que le capteur possède une faible précision et doit être recalibré. SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM Indique que les données du capteur sont de précision intermédiaire et qu'une recalibration améliorerait la lecture. SensorManager.SENSOR_STATUS_ACCURACY_HIGH Indique que le capteur a la précision la plus élevée possible. SensorManager.SENSOR_STATUS_UNRELIABLE Indique que les données du capteur ne sont pas fiable et qu'une calibration est nécessaire ou que la lecture n'est pas possible. Pour obtenir les données, nous devons nous enregistrer auprès du service comme écouteur de type SensorEventListener à l'aide de la méthode registerlistener() de la classe SensorManager. Spécifiez alors le capteur à observer et la fréquence à laquelle vous voulez recevoir les mises à jour. SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); Sensor gyroscope = gestioncapteurs.getdefaultsensor(sensor.type_gyroscope); Scrutation scrutation = new Scrutation(); gestioncapteurs.registerlistener(scrutation, gyroscope, SensorManager.SENSOR_DELAY_NORMAL); Comme nous le constatons, la récupération d'une instance de capteur s'associe à un taux de rafraîchissement des données qui peut être plus ou moins précis. Ce taux influence directement les performances de l'application ainsi que l'usage de la batterie. Plus le taux est élévé, plus les données sont rafraîchies et précises, et plus la consommation d'énergie est élevée. Le SensorManager contient les constantes suivantes (dans l'ordre décroissant de réactivité) pour sélectionner une fréquence appropriée : SensorManager.SENSOR_DELAY_FASTER Le taux de rafraîchissement le plus élevé. Les données sont récupérées aussi vite que possible par le système. SensorManager.SENSOR_DELAY_GAME Taux utilisable pour des applications nécessitant des données précises telles que les jeux. SensorManager.SENSOR_DELAY_NORMAL Taux normal utilisé par exemple par le système pour détecter les changements d'orientation. SensorManager.SENSOR_DELAY_UI Taux de rafraîchissement des mesures effectuées Le taux le plus bas utilisé dans les applications ne nécessitant qu'une faible précision ou ne sollicitant que très peu de données, comme les composants graphiques d'une interface utilisateur. La fréquence que vous sélectionnez n'est pas obligatoire. Le SensorManager peut répondre plus ou moins vite que ce que vous avez spécifié, mais il a tendance à être plus rapide. Pour minimiser le coût en ressources d'utilisation d'un capteur dans votre application, sélectionnez la fréquence la plus faible. Il est également important de désenregistrer votre SensorEventListener lorsque votre application n'a plus besoin de recevoir de mise à jour, en utilisant la méthode opposée unregisterlistener(). Il est d'ailleurs conseillé d'enregistrer et de désenregistrer votre SensorEventListener dans les méthodes de rappel onresume() et onpause() de vos activités pour garantir qu'ils ne sont utilisés que lorsque votre activité est active.

38 Interpréter les valeurs des capteurs La longueur et la composition des valeurs renvoyées par l'événement onsensorchanged() varient selon le capteur monitoré. Les détails sont donnés cidessous. Des détails complémentaires, du capteur d'orientation et du capteur de champ magnétique seront donnés dans les sections suivantes. Type de capteur Nombre Contenu des valeurs Commentaire TYPE_ACCELEROMETER 3 TYPE_GYROSCOPE 3 value[0] : Latéral value[1] : Longitudinal value[2] : Vertical value[0] : Azimut value[1] : Tangage value[2] : Roulis TYPE_LIGHT 1 value[0] : Luminosité TYPE_MAGNETIC_FIELD 3 TYPE_ORIENTATION 3 value[0] : Latéral value[1] : Longitudinal value[2] : Vertical value[0] : Azimut value[1] : Tangage value[2] : Roulis Accélération suivant trois axes en m/s 2. Le SensorManager contient un ensemble de constantes de gravité de la forme SensorManager.GRAVITY_*. Orientation de l'appareil en degrés suivant trois axes. Mesurée en lux. Le SensorManger contient un ensemble de constantes représentant différentes luminosités de la forme SensorManager.LIGNT_*. Champ magnétique local mesuré en microteslas (µt). Orientation de l'appareil en degrés suivant trois axes. TYPE_PRESSURE 1 value[0] : Pression Mesurée en kilopascals (KP). TYPE_PROXIMITY 1 value[0] : Distance Mesurée en mètres. TYPE_TEMPERATURE 1 value[0] : Température Mesurée en degré Celsius. Utiliser le compas, l'accéléromètre et les capteurs d'orientation La prise en compte du mouvement au sein des applications est possible grâce à la présence du capteur d'orientation et de l'accéléromètre dans la plupart des appareils récents. Ces dernières années, ces capteurs sont devenus de plus en plus répandus et ont trouvé leur place dans les manettes de contrôle de jeux et les smartphones. Les accéléromètres et les compas sont utilisés pour fournir des fonctionnalités fondées sur la direction, l'orientation et le mouvement de l'appareil. La disponibilté d'un compas et d'un accéléromètre dépend du matériel sur lequel votre application est exécutée. Lorsqu'ils sont disponibles, ils sont exposés via le SensorManager, vous permettant de : 1. Déterminer l'orientation courante de l'appareil. 2. Monitorer et tracer les changements d'orientation. 3. Savoir à quelle direction l'utilisateur fait face. 4. Monitorer l'accélération dans toutes les directions : verticale, latérale ou longitudinale. Cela ouvre quelques perspectives inhabituelles à vos applications. En monitorant l'orientation, la direction et le mouvement, vous pouvez : 1. Utiliser le compas et l'accéléromètre pour déterminer votre vitesse et votre direction. En conjonction avec une carte, un appareil photo et des services de géolocalisation, vous pouvez créer des interfaces en réalité augmentée qui rajoutent des données locales à un flux temps réel de l'appareil photo. 2. Créer des interfaces utilisateur s'ajustant dynamiquement à l'orientation de l'appareil. Android modifie déjà l'orientation de l'écran natif lorsque l'appareil passe du mode portrait au mode paysage et inversement. 3. Monitorer une accélération rapide pour déterminer si l'appareil tombe ou est lancé. 4. Mesurer le mouvement ou la vibration. Vous pourrez par exemple créer une application qui vous permet de vérouiller l'appareil. Si un mouvement est détecté pendant qu'il est vérouillé, une alerte SMS contenant la position courante peut être envoyée. 5. Créer des contrôles d'interface utilisateur qui utilisent les gestes et les mouvements comme mode d'entrée. Vous devrez toujours vérifier la disponibilité des capteurs requis et vous assurer que votre application se terminera proprement s'ils sont absents. Introduction aux accéléromètres Les accéléromètres, comme leur nom l'indique, sont utilisés pour mesurer l'accélération. Ils sont aussi parfois nommés capteurs de gravité. Les accéléromètres sont appelés capteurs de gravité en raison de leur incapacité à distinguer une accélération due à un mouvement d'une accélération due à la gravité. Par conséquent, une accélération détectant une accélération sur l'axe z (vertical) lira m/s 2 lorsque l'appareil sera immobile (cette valeur est disponible dans la constante SensorManager.STANDARD_GRAVITY). L'accélération est définie comme la modification de la vitesse d'un mouvement et les accéléromètres mesureront donc comment la vitesse de l'appareil change dans une direction donnée. En utilisant un accéléromètre, vous pourrez détecter les mouvements et, plus utilement, la variation de leur vitesse. Il est important de noter que les accéléromètres ne mesurent pas la vélocité, et vous ne pourrez donc pas mesurer directement la vitesse par une seule lecture de l'accéléromètre. A la place, vous devrez mesurer les changements d'accélération dans le temps. Généralement, vous serez intéressé par les changements d'accélération par rapport à un état repos ou par des mouvements rapides (repérables par des changements rapides dans l'accélération) comme des gestes de l'utilisateur. Dans le premier cas, vous devrez souvent calibrer l'appareil

39 pour calculer l'orientation et l'accélération initiales afin de les prendre en compte dans les résultats futurs. Détecter les changements d'accélération Comme nous l'avons découvert dans le tableau un peu plus haut, le SensorManager renvoie les modifications selon trois axes. Les valeurs de la propriété values du paramètre de type SensorEvent issu de l'écouteur SensorEventListener représente latérale, longitudinale et verticale dans cet ordre. Le SensorManager considère qu'un appareil est "au repos" lorsqu'il est posé face vers le haut en mode portrait sur une surface plane. L'accélération peut donc être mesurée selon trois directions : 1. latéral : gauche-droite : Accélération vers la gauche ou la droite, les valeurs positives représentent la droite et les négatives, la gauche. Une accélération latérale positive sera détectée sur un appareil posé à plat sur son dos en mode portrait et déplacé vers la droite. 2. longitudinal : avant-arriere : Accélération vers l'avant ou vers l'arrrière, une valeur positive représentant l'avant. Dans la même configuration que ci-dessus, une accélération longitudinale positive sera créée en déplaçant l'appareil vers l'avant. 3. verticale : haut-bas : Accélération vers le haut ou vers le bas, une valeur positive représentant le haut. Au repos, l'accéléromètre enregistre une valeur de 9.81 m/s 2 du fait de la gravité. Comme nous l'avons décrit plus haut, vous pouvez monitorer les changements dans l'accélération en utilisant un SensorEventListener. Enregistrer une implémentation de ce type d'écouteur avec le SensorManager en utilisant un objet Sensor de type Sensor.TYPE_ACCELEROMETER pour interroger l'accéléromètre. Dans ce deuxième petit projet, nous allons tester simplement l'accéléromètre intégré du terminal en affichant sous forme de texte la valeur des trois axes représentatifs. Nous prenons en compte l'accéléromètre par défaut en utilisant une fréquence normale. Encore une fois, l'émulateur d'android, nous le comprenons bien, ne permet pas d'obtenir les données de capteurs virtuels : il est donc nécessaire de travailler sur un vrai mobile. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="2px" android:background="#ffff00" > <TextView android:textsize="23dp" android:textcolor="#ff4400" /> <TextView android:textsize="23dp" android:textcolor="#ff4400" /> <TextView android:textsize="23dp" android:textcolor="#ff4400" /> </LinearLayout> package fr.btsiris.capteurs; res/layout/main.xml fr.btsiris.capteurs.accelerometre.java import android.app.activity; import android.content.context; import android.hardware.*; import android.os.bundle; import android.widget.textview; public class Accelerometre extends Activity implements SensorEventListener { private SensorManager gestioncapteurs; private Sensor accelerometre; private TextView lateral, longitudinal, vertical; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); lateral = (TextView) findviewbyid(r.id.lateral); longitudinal = (TextView) findviewbyid(r.id.longitudinal); vertical = (TextView) findviewbyid(r.id.vertical); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); accelerometre = gestioncapteurs.getdefaultsensor(sensor.type_accelerometer);

40 protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, accelerometre, SensorManager.SENSOR_DELAY_NORMAL); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this); public void onsensorchanged(sensorevent evt) { if (evt.sensor.gettype() == Sensor.TYPE_ACCELEROMETER) { lateral.settext("latéral... : "+evt.values[0]); longitudinal.settext("longitudinal... : "+evt.values[1]); vertical.settext("vertical... : "+evt.values[2]); public void onaccuracychanged(sensor capteur, int precision) { Mesurer la force gravitationnelle La force gravitationnelle est l'accélération comparée à la chute libre. Vous pouvez la mesurer en faisant la somme des accélérations dans les trois directions et en la comparant à la valeur de la chute libre. Dans l'exemple qui suit, nous allons créer un dispositif simple pour mesurer la force gravitationnelle en utilisant l'accéléromètre pour déterminer la force exercée sur l'appareil. Du fait de la gravité, la force exercée sur l'appareil au repos est de 9,81 m/s 2 vers le centre de la Terre. Dans cet exemple, vous allez l'annuler en la prenant en compte avec la constante SensorManager.STANDARD_GRAVITY. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <TextView android:gravity="center" android:textstyle="bold" android:textsize="32sp" android:text="centre" android:editable="false" android:singleline="true" android:layout_margin="10px" /> <TextView android:gravity="center" android:textstyle="bold" android:textsize="40sp" android:text="centre" android:editable="false" android:singleline="true" android:layout_margin="10px" /> </LinearLayout> package fr.btsiris.capteurs; res/layout/main.xml fr.btsiris.capteurs.gravitation.java import android.app.activity; import android.content.context; import android.hardware.*; import android.os.bundle; import android.widget.textview; import java.util.timer; import java.util.timertask; public class Gravitation extends Activity implements SensorEventListener { private SensorManager gestioncapteurs; private Sensor accelerometre; private TextView acceleration, accelerationmax; private float courante, maxi; public void oncreate(bundle savedinstancestate) {

41 super.oncreate(savedinstancestate); setcontentview(r.layout.main); acceleration = (TextView) findviewbyid(r.id.acceleration); accelerationmax = (TextView) findviewbyid(r.id.accelerationmax); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); accelerometre = gestioncapteurs.getdefaultsensor(sensor.type_accelerometer); Timer update = new Timer("update"); update.scheduleatfixedrate(new TimerTask() { public void run() { miseajour();, 0, 300); protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, accelerometre, SensorManager.SENSOR_DELAY_FASTEST); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this); public void onsensorchanged(sensorevent evt) { float x = evt.values[0]; float y = evt.values[1]; float z = evt.values[2]; float resultante = (float) Math.sqrt(x*x + y*y + z*z); courante = Math.abs(resultante-SensorManager.STANDARD_GRAVITY); if (courante > maxi) maxi = courante; public void onaccuracychanged(sensor capteur, int precision) { private void miseajour() { runonuithread(new Runnable() { public void run() { String Gs = ""+courante / SensorManager.STANDARD_GRAVITY ; acceleration.settext(gs); acceleration.invalidate(); String Gmax = "Max = "+ maxi / SensorManager.STANDARD_GRAVITY; accelerationmax.settext(gmax); accelerationmax.invalidate(); ); Les accéléromètres peuvent être très sensibles, et mettre à jour les TextView pour chaque changement d'accélération détecté peut être très coûteux. Il est plus judicieux de créer une méthode miseajour() qui se synchronise avec le thread de l'interface utilisateur en fonction d'un Timer. Déterminer votre orientation Le capteur d'orientation est une combinaison de capteurs de champ magnétique qui fonctionne comme un compas électronique et un accéléromètre pour déterminer le tangage et le roulis. En réalité, Android fournit deux alternatives pour déterminer l'orientation de l'appareil. Vous pouvez directement interroger le capteur d'orientation ou dériver celle-ci de l'accéléromètre et du capteur de champ magnétique. La dernière option prend plus de temps mais offre l'avantage d'une plus grande précision et de pouvoir modifier la trame de référence lorsque vous déterminer votre orientation. En utilisant la trame de référence standard, l'orientation de l'appareil est donnée selon trois dimensions comme l'illustre la liste ci-dessous : 1. Azimut : Axe qui pointe vers le haut : L'azimut (ou lacet) est la direction à laquelle l'appareil fait face suivant l'axe des x où 0/360 degrés est le nord, 90 degré l'est, 180 degré le sud et 270 degré l'ouest. 2. Tangage : Axe qui pointe vers l'avant : Le tangage représente l'ange de l'appareil autour de l'axe des y. Lorsque l'appareil est à plat sur son dos, la valeur est 0. Lorsqu'il est "debout" (sommet de l'appareil vers le haut), la valeur est -90. Lorsqu'il est vers le bas, la valeur est 90 et 180/-180 lorsqu'il est retourné. 3. Roulis : Axe qui pointe vers le côté droit : Le roulis représente le mouvement de l'appareil autour de l'axe des z. Lorsque l'appareil est posé à plat sur son dos, la valeur est 0, -90 lorsque l'écran fait face à la gauche et 90 lorsque l'écran fait face à la droite. Déterminer l'orientation avec le capteur d'orientation La façon la plus simple de monitorer l'orientation de l'appareil est d'utiliser le capteur d'orientation dédié. Créer et enregistrer un SensorEventListener avec le SensorManager en utilisant le capteur d'orientation par défaut.

42 SensorManager gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); Sensor orientation = gestioncapteurs.getdefaultsensor(sensor.type_orientation); gestioncapteurs.registerlistener(this, orientation, SensorManager.SENSOR_DELAY_NORMAL); Lorsque l'orientation change, la méthode onsensorchanged() de votre SensorEventListener est déclenchée. Le paramètre SensorEvent contient un tableau de float qui fournit l'orientation de l'appareil suivant les trois axes, respectivement l'azimut, le tangage et le roulis. Voici un petit projet qui permet d'évaluer l'orientation du mobile suivant les trois axes. Encore une fois, l'émulateur d'android, nous le comprenons bien, ne permet pas d'obtenir les données de capteurs virtuels : il est donc nécessaire de travailler sur un vrai mobile. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="10dp" android:background="#ffff00"> <TextView android:textsize="23dp" android:textcolor="#ff4400"/> <TextView android:textsize="23dp" android:textcolor="#ff4400" /> <TextView android:textsize="23dp" android:textcolor="#ff4400"/> </LinearLayout> package fr.btsiris.orientation; res/layout/main.xml fr.btsiris.capteurs.orientation.java import android.app.activity; import android.content.context; import android.hardware.*; import android.os.bundle; import android.widget.textview; public class Orientation extends Activity implements SensorEventListener { private SensorManager gestioncapteurs; private Sensor orientation; private TextView azimut, tangage, roulis; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); azimut = (TextView) findviewbyid(r.id.azimut); tangage = (TextView) findviewbyid(r.id.tangage); roulis = (TextView) findviewbyid(r.id.roulis); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); orientation = gestioncapteurs.getdefaultsensor(sensor.type_orientation); protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, orientation, SensorManager.SENSOR_DELAY_NORMAL); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this); public void onsensorchanged(sensorevent evt) { if (evt.sensor.gettype() == Sensor.TYPE_ORIENTATION) { azimut.settext("azimut... : "+evt.values[0]); tangage.settext("tangage... : "+evt.values[1]); roulis.settext("roulis... : "+evt.values[2]);

43 public void onaccuracychanged(sensor capteur, int precision) { Donner le cap Je vais modifier le projet précédent pour prendre en compte uniquement l'azimut et de donner ainsi le cap suivant les points cardinaux avec une rose des vents complète. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:background="#ffff00"> <TextView android:textsize="28sp" android:textstyle="italic bold" android:textcolor="#000000" android:gravity="center" /> <ImageView android:layout_width="wrap_content" android:layout_gravity="center" /> </LinearLayout> res/layout/main.xml package fr.btsiris.cardinaux; fr.btsiris.capteurs.pointscardinaux.java import android.app.activity; import android.content.context; import android.hardware.*; import android.os.bundle; import android.widget.textview; public class PointsCardinaux extends Activity implements SensorEventListener { private SensorManager gestioncapteurs; private Sensor orientation; private TextView azimut; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); azimut = (TextView) findviewbyid(r.id.azimut); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); orientation = gestioncapteurs.getdefaultsensor(sensor.type_orientation); protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, orientation, SensorManager.SENSOR_DELAY_FASTEST); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this); public void onsensorchanged(sensorevent evt) { if (evt.sensor.gettype() == Sensor.TYPE_ORIENTATION) { String cap = ""; float x = evt.values[0]; if (x<11.25 x>=348.75) cap ="Nord"; else if (x>11.25 && x<33.75) cap ="Nord-Nord-Est"; else if (x>33.75 && x<56.25) cap ="Nord-Est"; else if (x>56.25 && x<78.75) cap ="Est-Nord-Est"; else if (x>78.75 && x<101.25) cap ="Est"; else if (x> && x<123.75) cap ="Est-Sud-Est"; else if (x> && x<146.25) cap ="Sud-Est"; else if (x> && x<168.75) cap ="Sud-Sud-Est"; else if (x> && x<191.25) cap ="Sud"; else if (x> && x<213.75) cap ="Sud-Sud-Ouest"; else if (x> && x<236.25) cap ="Sud-Ouest";

44 else if (x> && x<258.75) cap ="Ouest-Sud-Ouest"; else if (x> && x<281.25) cap ="Ouest"; else if (x> && x<303.75) cap ="Ouest-Nord-Ouest"; else if (x> && x<326.25) cap ="Nord-Ouest"; else if (x> && x<348.75) cap ="Nord-Nord-Ouest"; azimut.settext(cap); public void onaccuracychanged(sensor capteur, int precision) { Pour valider l'étude de ce capteur d'orientation, je vous propose de réaliser une boussole qui nous donne le cap général Création d'une boussole <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.compas" android:versioncode="1" android:versionname="1.0"> <application android:label="boussole" > <activity android:name="compas" android:label="boussole" android:screenorientation="portrait"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="5dp" android:background="#ffbb00" > <fr.btsiris.compas.vuecompas android:layout_height="fill_parent" /> </LinearLayout> AndroidManifest.xml res/layout/main.xml package fr.btsiris.compas; fr.btsiris.compas.compas.java import android.app.activity; import android.content.context; import android.hardware.*; import android.os.bundle; public class Compas extends Activity implements SensorEventListener { private VueCompas boussole; private SensorManager gestioncapteurs; private Sensor orientation; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); boussole = (VueCompas) findviewbyid(r.id.boussole); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); orientation = gestioncapteurs.getdefaultsensor(sensor.type_orientation); protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, orientation, SensorManager.SENSOR_DELAY_FASTEST); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this); public void onsensorchanged(sensorevent evt) { if (evt.sensor.gettype() == Sensor.TYPE_ORIENTATION) {

45 boussole.setazimut(evt.values[0]); boussole.invalidate(); public void onaccuracychanged(sensor capteur, int precision) { Cette fois-ci, nous avons besoin d'une vue personnalisée qui s'occupe de réaliser les différents tracés pour la boussole elle-même. package fr.btsiris.compas; fr.btsiris.compas.vuecompas.java import android.content.context; import android.graphics.*; import android.util.attributeset; import android.view.view; public class VueCompas extends View { private Paint texte, cercle, marque; private int hauteurtexte; private float azimut; public void setazimut(float azimut) { this.azimut = azimut; public VueCompas(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); init(); public VueCompas(Context context, AttributeSet attrs) { super(context, attrs); init(); public VueCompas(Context context) { super(context); init(); private void init() { cercle = new Paint(Paint.ANTI_ALIAS_FLAG); cercle.setcolor(color.red); texte = new Paint(Paint.ANTI_ALIAS_FLAG); texte.setcolor(color.yellow); texte.settextsize(20); hauteurtexte = (int) texte.measuretext("yy"); marque = new Paint(Paint.ANTI_ALIAS_FLAG); marque.setcolor(color.yellow); marque.setstrokewidth(3); protected void onmeasure(int largeur, int hauteur) { int dimension = Math.min(mesure(largeur), mesure(hauteur)); setmeasureddimension(dimension, dimension); private int mesure(int dimension) { int modemesure = MeasureSpec.getMode(dimension); int taillemesure = MeasureSpec.getSize(dimension); if (modemesure == MeasureSpec.UNSPECIFIED) return 200; else return taillemesure; protected void ondraw(canvas canvas) { int px = getmeasuredwidth() / 2; int py = getmeasuredheight() / 2; int rayon = Math.min(py, py); canvas.drawcircle(px, py, rayon, cercle); canvas.save(); canvas.rotate(-azimut, px, py); int largeurtexte = (int) texte.measuretext("o"); int cardinalx = px - largeurtexte / 2; int cardinaly = py - rayon+hauteurtexte; for (int i=0; i<8; i++) { canvas.drawline(px, py-rayon, px, py-rayon+15, marque); canvas.save(); canvas.translate(0, hauteurtexte); String[] cap = {"N", "NE", "E", "SE", "S", "SO", "O", "NO"; if (i==0) { int flechey = 2*hauteurTexte;

46 canvas.drawline(px, flechey, px-5, 3*hauteurTexte, marque); canvas.drawline(px, flechey, px+5, 3*hauteurTexte, marque); canvas.drawline(px, flechey, px, 6*hauteurTexte, marque); canvas.drawtext(cap[i], cardinalx, cardinaly, texte); canvas.restore(); canvas.rotate(45, px, py); canvas.restore(); Création d'un compas avec un horizon artificiel Nous allons modifier maintenant la classe VueCompas afin d'intégrer un horizon artificiel qui nous précise à la fois la valeur du tangage et du roulis. package fr.btsiris.compas; fr.btsiris.compas.vuecompas.java import android.content.context; import android.graphics.*; import android.util.attributeset; import android.view.view; public class VueCompas extends View { private Paint texte, cercle, marque; private int hauteurtexte; private float azimut; private float tangage; private float roulis; public float getazimut() { return azimut; public void setazimut(float azimut) { this.azimut = azimut; public float getroulis() { return roulis; public void setroulis(float roulis) { this.roulis = roulis; public float gettangage() { return tangage; public void settangage(float tangage) { this.tangage = tangage; public VueCompas(Context context, AttributeSet attrs, int defstyle) { super(context, attrs, defstyle); init(); public VueCompas(Context context, AttributeSet attrs) { super(context, attrs); init(); public VueCompas(Context context) { super(context); init(); private void init() { setfocusable(true); cercle = new Paint(Paint.ANTI_ALIAS_FLAG); // Mise en place de l'anti-aliasing pout ne pas avoir d'effet d'escalier dans les tracés cercle.setcolor(color.red); cercle.setstrokewidth(1); // dimension du trait (STROKE) cercle.setstyle(paint.style.fill_and_stroke); // le bord et le contenu va être pris en compte texte = new Paint(Paint.ANTI_ALIAS_FLAG); texte.setcolor(color.yellow); hauteurtexte = (int) texte.measuretext("yy"); marque = new Paint(Paint.ANTI_ALIAS_FLAG); marque.setcolor(color.yellow); protected void onmeasure(int largeur, int hauteur) { int dimension = Math.min(mesure(largeur), mesure(hauteur)); setmeasureddimension(dimension, dimension); private int mesure(int dimension) { int modemesure = MeasureSpec.getMode(dimension); int taillemesure = MeasureSpec.getSize(dimension); if (modemesure == MeasureSpec.UNSPECIFIED) return 200; else return taillemesure;

47 protected void ondraw(canvas canvas) { int px = getmeasuredwidth() / 2; int py = getmeasuredheight() / 2; int rayon = Math.min(py, py); canvas.drawcircle(px, py, rayon, cercle); canvas.save(); canvas.rotate(-azimut, px, py); int largeurtexte = (int) texte.measuretext("o"); int cardinalx = px - largeurtexte / 2; int cardinaly = py - rayon+hauteurtexte; for (int i=0; i<24; i++) { canvas.drawline(px, py-rayon, px, py-rayon+10, marque); canvas.save(); canvas.translate(0, hauteurtexte); if (i % 6 == 0) { String cap = ""; switch (i) { case 0 : cap = "N"; int flechey = 2*hauteurTexte; canvas.drawline(px, flechey, px-5, 3*hauteurTexte, marque); canvas.drawline(px, flechey, px+5, 3*hauteurTexte, marque); break; case 6 : cap = "E"; break; case 12 : cap = "S"; break; case 18 : cap = "O"; break; canvas.drawtext(cap, cardinalx, cardinaly, texte); else if (i % 3 == 0) { String angle = String.valueOf(i*15); float largeurangle = texte.measuretext(angle); int angletextex = (int) (px - largeurangle / 2); int angletextey = py - rayon + hauteurtexte; canvas.drawtext(angle, angletextex, angletextey, texte); canvas.restore(); canvas.rotate(15, px, py); canvas.restore(); float d = 2*rayon; RectF cercleroulis = new RectF(d/3-d/7, d/2-d/7, d/3+d/7, d/2+d/7); marque.setstyle(paint.style.stroke); // Tracé du coutour uniquement canvas.drawoval(cercleroulis, marque); marque.setstyle(paint.style.fill); // Tracé de l'intérieur uniquement canvas.save(); canvas.rotate(roulis, d/3, d/2); canvas.drawarc(cercleroulis, 0, 180, false, marque); canvas.restore(); RectF cercletangage = new RectF(2*d/3-d/7, d/2-d/7, 2*d/3+d/7, d/2+d/7); marque.setstyle(paint.style.stroke); // Tracé du coutour uniquement canvas.drawoval(cercletangage, marque); marque.setstyle(paint.style.fill); // Tracé de l'intérieur uniquement canvas.drawarc(cercletangage, 0-tangage/2, 180+tangage, false, marque); Déplacement d'une boule suivant l'horizontalité du mobile Dernier exercice concernant l'accéléromètre en vous proposant une boule que se déplace suivant l'horizontalité de votre smartphone. Il faut par contre récupérer une image de fond et une image de la boule que vous placerez dans la ressource drawable. package fr.btsiris.niveau; fr.btsiris.niveau.niveau.java import android.app.activity; import android.content.context; import android.graphics.*; import android.hardware.*; import android.os.bundle; import android.view.*; public class Niveau extends Activity implements SensorEventListener { private SensorManager gestioncapteurs; private Sensor accelerometre; private SurfaceView surface; private SurfaceHolder holder; private Bitmap boule, fond; private float bx, by, vx, vy, tx, ty;

48 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); boule = BitmapFactory.decodeResource(getResources(), R.drawable.boule); fond = BitmapFactory.decodeResource(getResources(), R.drawable.fond); surface = new SurfaceView(this); holder = surface.getholder(); gestioncapteurs = (SensorManager) getsystemservice(context.sensor_service); accelerometre = gestioncapteurs.getdefaultsensor(sensor.type_accelerometer); setcontentview(surface); protected void onstart() { super.onstart(); gestioncapteurs.registerlistener(this, accelerometre, SensorManager.SENSOR_DELAY_GAME); protected void onstop() { super.onstop(); gestioncapteurs.unregisterlistener(this, accelerometre); public void onsensorchanged(sensorevent evt) { if (evt.sensor.gettype() == Sensor.TYPE_ACCELEROMETER) { // Ajout les valeurs du capteur aux variables vx -= evt.values[0]; vy += evt.values[1]; // Déplacement de la boule bx += vx; by += vy; // Récupération de la taille de l'écran tx = surface.getwidth() - boule.getwidth(); ty = surface.getheight() - boule.getheight(); // Empêcher la boule de sortir if (bx<0) { bx = 0; vx = 0; if (by<0) { by = 0; vy = 0; if (bx>tx) { bx = tx; vx = 0; if (by>ty) { by = ty; vy = 0; // Redessiner Canvas dessin = holder.lockcanvas(); if (dessin==null) return; dessin.drawbitmap(fond, 0, 0, null); dessin.drawbitmap(boule, bx, by, null); holder.unlockcanvasandpost(dessin); public void onaccuracychanged(sensor capteur, int precision) { J'aimerais, au travers de cet exemple, puisque je ne l'ai pas encore fait, vous parler des tracés personnalisés. Tracés personnalisés - Graphismes 2D 1. Lorsque vous devez utiliser un composant dont l'apparence est à votre libre initiative, la première chose à faire consiste à créer un nouveau composant qui hérite de la classe View et de redéfinir un certain nombre de méthode dont vous avez la description cidessous : 2. ondraw(canvas) : Méthode automatiquement appelée par le système lorsque la vue doit être affichée à l'écran. La méthode fournit un Canvas en paramètre qui est utilisé comme support des dessins. Je rappelle qu'il est possible d'imposer le réaffichage au travers de la méthode invalidate() du composant à réafficher. 3. La méthode ondraw() contient toute la magie. Vous créez un nouveau widget à partir de zéro car vous voulez créer une interface visuelle entièrement nouvelle. La paramètre Canvas de la méthode ondraw() va vous permettre de donner vie à votre imagination. 4. Android fournit une palette d'outils pour vous y aider, à l'aide de divers objets Paint. La classe Canvas comprend toutes les méthodes drawxxx() pour le dessin d'objet 2D primitifs comme les cercles, lignes, rectangles, textes et Drawable. Elle supporte également les transformations qui permettent la rotation, le déplacement et le redimmensionnement du Canvas pendant que nous dessinons. 5. Lorsque vous utilisez ces outils en les combinant à des Drawable et à la classe Paint (qui offre divers stylos et outils de remplissage personnalisables), la complexité et les détails que vos contrôles pourront afficher ne seront limités que par la taille de l'écran et la puissance du processeur. 6. onsizechanged(int, int, int, int) : Cette méthode peut être redéfinie pour être notifié d'un changement de taille de la vue : les deux premiers paramètres définissent les anciennes largeur et hauteur, alors que les deux derniers sont les nouvelles dimensions. Elle est généralement utilisée pour initialiser certianes propriétés de la vue. 7. onmeasure(int, int) : Cette méthode est automatiquement exécutée par le système lorsque ce dernier souhaite obtenir les dimensions choisies par la vue. Le fonctionnement de cette méthode est un peu particulier car il est impératif d'appeler la méthode setmeasureddimension(int, int) lors de son exécution. Cette commande informe le système des dimensions (largeur et hauteur)

49 choisies. 8. La méthode onmeasure() est appelée lorsque le contrôle parent dispose ses contrôles enfants. Il leur demande quel est l'espace dont ils ont besoin et passe deux paramètres la largeur et la hauteur. Ils spécifient l'espace disponible pour le contrôle ainsi que quelques métadonnées décrivant cet espace. Plutôt que renvoyer un résultat, vous passez la hauteur et la largeur de la vue à la méthode setmeasuredimension(). 9. Les paramètres correspondant à la largeur et à la hauteur sont passés sous forme d'entiers pour des raisons d'efficacité. Avant de pouvoir être utilisés, il sdoivent être décodés à l'aide des méthodes statiques getmode() et getsize() de la classe MeasureSpec. 10. Selon la valeur de mode, size représente soit l'espace maximal (fill_parent) disponible pour le contrôle (si AT_MOST), soit la taille exacte (wrap_content) de votre contrôle (si EXACTLY). Si vous indiquez UNSPECIFIED, votre contrôle ne saura pas ce que la taille représente. 11. En indiquant EXACTLY, le parent force la View à se placer dans un espace à la taille spécifiée. Dans le mode AT_MOST, il demande à la View quelle est la taille de l'espace qu'elle veut occuper, la limite haute étant spécifiée. Dans la plupart des cas, la valeur que vous renverrez sera la même. 12. Dans les deux cas, vous devrez considérer ces limites comme absolues. Dans certaines circonstances, il peut être néanmoins approprié de renvoyer une dimension allant au-delà. Vous laisserez dans ce cas la parent choisir comment gérer le dépassement en utilisant des techniques comme le clipping (bornage du résultat) ou le scrolling (défilement). 13. Android fournit diverses classes permettant de dessiner "en bas niveau". Les trois principales sont décrites ci-dessous : 14. Canvas : En effectuant une analogie avec un peintre, nous pourrions comparer le Canvas à la toile (c'est d'ailleurs l'expression anglaise pour "toile") sur laquelle l'artiste dessine. Il autorise des opérations basiques, comme les translations, les rotations, etc. permettant de mimer les gestes du peintre sur la toile. Utilisé seul, un Canvas est clairement inutile. Pour tracer et dessiner sur cette feuille blanche (ou plutôt transparente), il est nécessaire d'utiliser un pinceau. Un Canvas n'a pas de taille ni d'existence réelle en tant que tel. Il doit être employé en conjonction d'un Paint et d'un Bitmap décrit ci-dessous. 15. Paint : Traduit de façon littérale, le pinceau (ou Paintbrush) est un objet permettant de définir la forme et le style du dessin qui sera généré. Paint est donc porteur d'informations comme la couleur ou la largeur du trait, l'état de l'anti-aliasing, le style du texte, la façon dont est remplie la forme, etc. 16. Bitmap : Il s'agit en quelque sorte de l'objet affiché. Un Bitmap est une image brute composée de pixels sur laquelle un Canvas est nécessaire lorsque nous souhaitons y dessiner. 17. Pour se déplacer sur le Canvas, il est possible d'utiliser des méthodes telles que translate(float, float), rotate(float), scale(float, float), etc. Dès lors qu'une de ces méthodes est appelée, l'état du Canvas est impacté. Ainsi, si nous passons d'un état A à B en utilisant la transformation x, puis de B à C par la transformation y, il est nécessaire de calculer les transformations inverses y' et x' pour revenir à l'état A. Cette opération est parfois difficile, il est alors possible de sauvegarder l'état actuel d'un Canvas grâce à la méthode save(). L'état du Canvas peut ensuite être restitué par un simple appel à restore(). 18. invalidate() : Cette méthode permet d'armer un bit système sur une vue décrivant son état "impropre". Cette méthode est donc extrêmement importante car elle informe le système de la nécessité de redessiner la vue en question. Lorsqu'un appel à invalidate() est effectué, Android mémorise la demande et exécutera une passe "dessin" dès que possible (au prochain tour de boucle événementielle). Ressources Drawable Développer des vues personnalisées, comme l'exemple que nous venons de traiter, permet d'avoir un contrôle extrêmement précis sur les objets dessinés. C'est en réalité la façon la plus "bas-niveau" qui s'offre au développeur lorsqu'il s'agit de dessiner à l'écran. Malheureusement, dessiner "bas-niveau" est une opération longue, sujette à erreur et souvent difficile à réaliser. En effet, le développement "bas-niveau" d'interfaces graphiques oblige le programmeur à gérer manuellement un grand nombre de paramètres habituellement gérés par le système au niveau "vue", tels que la densité, la résolution, l'orientation de l'écran, etc. Afin de faciliter la création de graphisme, Android offre une fonctionnalité extrêmement pratique : les Drawables. La notion de Drawable sous Android est assez large puisqu'elle définit tout ce qui peut être dessiné. Ainsi une couleur, une forme, un dégradé, une image, etc., sont considérés comme des Drawables. Android utilise largement les Drawable, il en fait donc un composant essentiel à son fonctionnement. L'utilité des Drawables réside dans les points donnés ci-après : 1. Un Drawable est une abstraction de ce qui est "dessinable". Cette généralisation facilite grandement l'utilisation des graphismes tout en donnant facilement accès à des fonctionnalités plus évoluées que de simples Bitmap et Canvas. 2. Les Drawables gèrent de façon intrinsèque leur taille et s'adaptent automatiquement à un changement de dimensions. Cette particularité permet de s'accomoder facilement des résolutions variées de l'écosystème des terminaux Android. 3. Dans l'exemple précédent, il a fallu étendre View pour développer nos propres graphismes. Le concept de Drawable évite cet héritage en autorisant la modification d'une vue prédéfinie de façon native. La classe View autorise, par exemple, le développeur à modifier son fond par la méthode setbackgrounddrawable(drawable). Ainsi, vous pouvez facilement imaginer une View disposant d'un fond dégradé, coloré ou même représentant une image. Architecture des Drawables L'architecture des Drawables est largement inspirée de celle des View. En effet, un Drawable peut contenir d'autres Drawables "feuille". Nous retrouvons donc le modèle de conception "Composite". La principale différence entre ces deux architectures réside dans l'impossibilité d'instancier un Drawable. En effet, cette classe est abstraite et oblige le développeur à implémenter un certain nombre de méthodes lorqu'il souhaite créer sa propre instance de Drawable. Principales fonctionnalités des Drawables L'obtention d'une référence sur un Drawable s'effectue par l'intermédiaire d'un objet de type android.content.res.resources :

50 Drawable drawable = context.getresources().getdrawable(r.drawable.balle); Dès lors, il est possible de modifier la plupart des propriétés du Drawable. Parmi les fonctionnalités gérées par les Drawables, nous retrouvons : 1. La taille et l'emplacement à l'aide des méthodes suivantes: setbounds(rect) Définit le rectangle dans lequel le Drawable sera affiché. Cette méthode permet donc de définir la taille du Drawable, mais également son origine. getintrinsicwidth(), getintrinsicheight() Retourne, si possible, la taille intrinsèque du Drawable. Ainsi, les dimensions intrinsèques d'une image sont les dimensions de l'image elle-même, alors qu'un Drawable de type "couleur" n'a pas de taille intrinsèque (la valeur retournée par défaut est alors égale à -1). getminimumwidth(), getminimumheight() Retourne les dimensions minimales suggérées par le Drawable. 2. L'état : setstate(int) : Un Drawable peut changer son apparence en fonction de l'état dans lequel il se trouve. A titre d'exemple, un bouton n'a pas la même apparence lorsqu'il est dans l'état pressé et lorsqu'aucune action n'est exécutée. 3. Le niveau : Certains Drawables changent d'apparence en fonction du niveau courant. Ainsi, il est possible de définir des Drawables qui changent d'apparence facilement. Ces Drawables sont principalement utilisés pour représenter des barres de progression ou des icônes exprimant le niveau courant d'une entité graduée (qualité de la réception, état de la batterie, etc.). 4. Les propriétés d'affichage : La classe Drawable autorise la modification de l'apparence générale du graphique sous-jacent. Des méthodes permettant de modifier l'opacité - setalapha(int) - ou le filtre de couleur - setcolorfilter(colorfilter) - sont ainsi disponibles. Tour d'horizon des Drawables A l'instar d'une View, un Drawable peut être instancié via XML ou Java. Dans la suite de cette rubrique, nous allons principalement nous attarder sur le code XML exactement pour les mêmes raisons que celles évoquées pour la mise en oeuvre des View. Il est tout de même important de conserver à l'esprit que l'instanciation via XML n'est en réalité qu'une surcouche de l'instanciation via du code Java. A ce titre, les foncitonnalités accessibles dans un fichier XML sont un sous-ensemble de celles offertes par Java. Il est donc probable que certaines fonctionnalités ne soient pas réalisables via XML, mais le soient avec Java. Android contient de nombreux types de ressources Drawable simples qui peuvent être entièrement définies en XML. Ces ressources comprennent les classes ColorDrawable, ShapeDrawable et GradientDrawable. Elles sont stockées dans le dossier res/drawable et peuvent ainsi être référencées dans du code en utilisant leurs noms de fichiers XML en minuscules. Si ces Drawables sont définis en XML et que vous spécifiiez leurs attributs en pixels à densité indépendante, le moteur d'exécution les mettra à l'échelle.tout comme les graphiques vectoriels, ces Drawables peuvent être dynamiquement mis à l'échelle afin d'être affichés correctement et sans artifices, et ce quelles que soient la taille de l'écran, sa résolution ou sa densité en pixels. Les Drawables gradient constituent une exception à cette règle puisque le rayon du gradient doit être défini en pixels. Comme nous le verrons, vous pouvez utiliser ces Drawables en les combinant à des Drawables transformables ou composites. Ensemble, ils peuvent constituer des éléments d'interface utilisateur dynamiques et évolutifs, requérant moins de ressources et agréables à afficher sur n'importe quel écran. ColorDrawable Le ColorDrawable est une objet n'affichant qu'une unique couleur. De façon assez logique, le ColorDrawable ne respecte pas le ColorFilter et les dimensions données par setbounds(rect). Il s'affiche en réalité dans l'intégralité de la zone de clip. Le ColorDrawable n'est en fait qu'une encapsulation d'une couleur dans la notion de Drawable. Puisqu'Android gère indépendamment les ressources de type "couleur", nous utiliserons rarement ce type de Drawable. Il est néanmoins à noter qu'un setbackgroundcolor(int) génère en réalité un ColorDrawable à partir de la couleur donnée. Le ColorDrawable est le plus simple des Drawables définis en XML. Il vous permet d'afficher une image basée sur une couleur unie. Les ColorDrawables sont définis dans des fichiers XML situés dans le dossier de ressources drawable, en utilisant la balise <color>. A titre d'exemple, je vous propose de reprendre le projet sur la localisation afin de prévoir un fond de couleur unie personnalisée dans l'activité principale de l'application.

51 <color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ffcc00" /> <?xml version="1.0" encoding="utf-8"?> <resources> <color name="texte">#0000ff</color> </resources> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="10px"> <EditText android:hint="rue" /> <EditText android:hint="ville" /> <Button android:text="rechercher" android:onclick="localiser"/> <TextView android:textstyle="bold" android:textsize="18sp" /> <TextView android:textstyle="bold" android:textsize="18sp" /> </LinearLayout> /* AUTO-GENERATED FILE. DO NOT MODIFY. */ res/drawable/fond.xml res/values/couleurs.xml res/layout/main.xml fr.btsiris.localisation.r.java package fr.btsiris.localisation; public final class R { public static final class attr {

52 public static final class color { public static final int texte=0x7f040000; public static final class drawable { public static final int fond=0x7f020000; public static final class id { public static final int latitude=0x7f050002; public static final int longitude=0x7f050003; public static final int rue=0x7f050000; public static final int ville=0x7f050001; public static final class layout { public static final int main=0x7f030000; ShapeDrawable Les ShapeDrawables (forme) vous permettent de définir des formes primitives simples en spécifiant leurs dimensions, fond et coutour, à l'aide de la balise <shape>. oval Chaque shape est constitué d'un type (spécifié par l'attribut shape), d'attributs de dimension et de sous-noeuds spécifiant l'épaisseur du conteour et la couleur de fond. Simple ovale. rectangle Android supporte actuellement les formes suivantes comme valeur de l'attribut shape Supporte le sous-noeud <corners> avec l'attribut radius pour créer un rectangle aux angles arrondis. Il est même possible de régler chaque coin particulier au moyen des attributs spécifiques suivants : topleftradius, toprightradius, bottomleftradius, bottomrightradius. ring line Supporte les attributs innerradius et thickness qui vous permettent de spécifier respectivement le rayon intérieur de la forme anneau ainsi que son épaisseur. Vous pouvez également utiliser innerradiusratio et/ou thicknessratio pour définir le rayon intérieur de l'anneau et son épaisseur proportionnellement à sa largeur (un rayon intérieur d'un quart de la largeur aura la valeur 4). Ligne horizontale dont la largeur traverse la totalité du composant View. Cette forme particulière requiert le sous-noeud <stroke> pour définir la largeur et la couleur de la ligne. 1. Le sous-noeud <stroke> permet de spécifier la bordure des formes à l'aide des attributs width et color. Il existe deux autres attributs dashgap et dashwidth qui permettent d'avoir des lignes discontinues en spécifiant respectivement la distance entre les traits et la longueur des traits. 2. Vous pouvez également inclure un noeud <padding> pour positionner votre forme sur le Canvas de manière relative. 3. De façon plus utile, vous pouvez inclure un sous-noeud spécifiant la couleur de fond. Le cas le plus simple consiste à utiliser le noeud <solid> avec l'attribut color pour définir la couleur de fond unie. 4. Le dégradé de couleur, qui sera étudié spécifiquement dans la prochaine section, se traduit au moyen du sous-noeud <gradient>. 5. Le sous-noeud <size> permet de préciser une dimension spécifique à l'aide des attributs width et height. 6. uselevel : Cet attribut de la balise <shape>, normalement associé à LevelListDrawable, doit être spécifié à false si vous voulez qu'il soit visible Je reprend l'exemple précédent, mais cette fois-ci, ce sont les TextView qui profitent du fond créer dans le drawable.

53 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <solid android:color="#ffff00" /> <stroke android:width="5dp" android:color="#ffcc00" /> <corners android:radius="15dp" /> <padding android:left="15dp" android:top="10dp" android:right="15dp" android:bottom="10dp" /> </shape> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"android:padding="5px"> <EditText android:hint="rue" /> <EditText android:hint="ville" /> <Button android:text="rechercher" android:onclick="localiser"/> <TextView android:textstyle="bold" android:textsize="18sp" /> <TextView android:textstyle="bold" android:textsize="18sp" /> </LinearLayout> res/drawable/fond.xml res/layout/main.xml GradientDrawable Comme son nom le laisse à penser, le GradientDrawable permet de reproduire des dégradés de couleur. Chaque gradient définit une transition harmonieuse entre deux ou trois couleurs de façon linéaire, radiale ou circulaire. Les GradientDrawables sont définis par la balise <gradient> comme un sous-noeud de la définition d'un ShapeDrawable, au travers de la balise principale <shape>. Chaque GradientDrawable requiert au moins un attribut startcolor et un attribut endcolor et supporte l'attribut optionnel middlecolor., ainsi que l'attribut type.

54 L'attribut type vous permet de choisir votre gradient parmi les suivants linear Le type par défaut. Il affiche une transition de couleurs directe de startcolor à endcolor suivant un angle défini par l'attribut angle. radial Dessine un gradient radial de startcolor à endcolor depuis le bord externe de la forme jusqu'à son centre. Il requiert un attribut gradientradius qui spécifie le rayon de la transition du gradient en pixels. Il supporte également optionnellement les attributs centerx et centery pour décaler le centre du gradient. Le rayon étant défini en pixels, il ne sera pas mis à l'échelle dynamiquement pour différentes densités de pixels. Pour minimiser l'effet d'escalier, vous pourrez spécifier différentes valeurs de rayon pour différentes résolutions d'écrans. sweep Dessine un gradient circulaire de startcolor à endcolor le long du bord externe de la forme parent (un anneau, typiquement). Voici ce que nous pouvons obtenir respectivement avec un dégradé linéaire, un radial et radial dont le centre est choisi sur le bord gauche en bas : <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="15dp"> <EditText android:hint="rue" /> <EditText android:hint="ville" /> <Button android:text="rechercher" android:onclick="localiser"/> <TextView android:textstyle="bold" android:textsize="18sp" /> <TextView android:textstyle="bold" res/layout/main.xml

55 android:textsize="18sp" /> </LinearLayout> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <stroke android:width="3dp" android:color="#ffff00" /> <corners android:radius="15dp" /> <gradient android:startcolor="#ffff00" android:endcolor="#ffff00" android:centercolor="#ff0000" android:type="linear" android:angle="45" /> </shape> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <stroke android:width="3dp" android:color="#ffff00" /> <corners android:radius="15dp" /> <gradient android:startcolor="#ff0000" android:endcolor="#ffff00" android:type="radial" android:gradientradius="300" /> </shape> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <stroke android:width="3dp" android:color="#ffff00" /> <corners android:radius="15dp" /> <gradient android:startcolor="#ff0000" android:endcolor="#ffff00" android:type="radial" android:gradientradius="500" android:centerx="0" android:centery="1" /> </shape> (linéaire) res/drawable/fond.xml (radial) res/drawable/fond.xml (radial avec une valeur de position) res/drawable/fond.xml BitmapDrawable Le BitmapDrawable est probablement le plus utilisé des Drawables. Cet objet est la représentation sous forme de Drawable d'une image de format jpg ou png. Nous récupérons donc souvent ce dernier par un simple getdrawable() sur la ressource "brute". Il est néanmoins possible de créer un BitmapDrawable via XML à l'aide de la balise <bitmap /> et d'utiliser les paramètres avancés. android:antialias Attributs disponibles Lors de l'affichage de l'image, il est possible de proposer un anti-aliasing en positionnant cet attribut à true. Cet attribut est utile dans le cas d'un rééchantillonage automatique de votre image à visualiser sur la totalité de l'écran, si cette dernière est plus petite que ce que propose l'écran. android:filter Dans le même ordre d'idée, il est possible de valider l'attribut filter (true) lorsque vous avez besoin que votre image soit plus douce (flou glaussien). android:gravity A l'instar du FrameLayout, le BitmapDrawable gère la notion de gravité. Il est ainsi possible de positionner une image à l'intérieur de la zone d'affichage du Drawable (les bounds). Par défaut, la gravité est à fill ce qui signifie que l'image est étirée pour remplir intégralement les bounds. Voici l'ensemble des valeurs possibles pour cet attribut : top, bottom, left, right, center_vertical, fill_vertical, center_horizontal, fill_horizontal, center, fill, clip_vertical, clip_horizontal, start et end. android:src Resource de l'image, votre fichier original. android:tilemode Le BitmapDrawable gère également la notion de répétition d'un motif. Cette possibilité est en quelque sorte l'équivalent de la propriété CSS background-repeat. En réalité, voici les valeurs possibles pour cet attribut : disabled (mode par défaut), clamp (Réplication de la couleur de la bordure), repeat (répétition en x et en y) et mirror(répétition également, mais avec un effet mirroir - symétrie).

56 Toujours par rapport au projet précédent, je propose de placer une trame de fond prédéfinie dans l'activité principale, que nous pouvons retrouver dans les ressources android dans les contantes entières qui sont à votre disposition, ici android.r.drawable.dialog_frame : <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:tilemode="repeat" /> res/drawable/fond.xml Drawables composites Les Drawables composites vous permettent de combiner et de manipuler d'autres ressources Drawable. Tout Drawable peut être utilisé dans les ressources composites qui suivent, y compris les bitmaps, les shapes et les colors. De même, ces nouveaux Drawables peuvent être utilisés les uns dans les autres et assignés à des Views tout comme les autres. Drawables transformables Vous pouvez mettre à l'échelle et faire tourner des Drawables à l'aide des classes ScaleDrawable et RotateDrawable. Ces Drawables transformables sont particulièrement utiles pour créer des barres de progression ou des Views animées. ScaleDrawable Utilisez les attributs scaleheight et scalewidth dans la balise <scale> pour définir la hauteur et la largeur voulues relativement aux limites du Drawable d'origine. Utilisez l'attribut scalegravity pour contrôler le point d'ancrage de l'image mise à l'échelle. RotateDrawable Utilisez fromdegrees et todegrees dans l'attribut <rotate> pour définir le point de départ et la fin de l'angle de rotation autour d'un point pivot. Définissez le pivot à l'aide des attributs pivotx et pivoty, en spécifiant respectivement un pourcentage de la largeur et de la hauteur du Drawable à l'aide de la notation nn%. Pour appliquer une mise à l'échelle et une rotation au moment de l'exécution, utilisez la méthode setlevel() sur l'objet View portant le Drawable afin d'effectuer un mouvement entre les valeurs de départ et de fin (0 à ). Lors d'un mouvement à travers plusieurs niveaux, le niveau 0 représente l'angle de départ (ou le plus petit résultat de la mise à l'échelle). Le niveau représente la fin de la transformation (l'angle de fin ou la plus haute mise à l'échelle). Les listing ci-dessous montrent les définitions XML de deux Drawables transformables et le code Java correspondant qui permet de les manipuler après les avoir assignés à une View image. <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromdegrees="0" android:todegrees="90" android:pivotx="50%" android:pivoty="50%" /> <scale

57 xmlns:android="http://schemas.android.com/apk/res/android" android:scaleheight="100%" android:scalewidth="100%" /> ImageView rotationimage = (ImageView) findviewbyid(r.id.rotationimage); ImageView echelleimage = (ImageView) findviewbyid(r.id.echelleimage); rotationimage.setimagelevel(5000); // Fait pivoter l'image de 50%. echelleimage.setimagelevel(5000); // Met l'image à une échelle de 50% de sa taille finale. Drawable layer Le LayerDrawable vous permet d'assembler plusieurs ressources Drawable les unes sur les autres. Si vous définissez un tableau de Drawables partiellement transparents, vous pourrez les empiler les uns sur les autres afin de créer des combinaisons de formes dynamiques et de transformations. De façon similaire, vous pouvez utiliser les LayerDrawables comme source des Drawables transformables décrits dans la section précédente ou des Drawables state list et level list que nous présenterons plus loin. Le listing suivant montre un LayerDrawable. Il est défini via la balise <layer-list>. Dans cette balise, l'attribut drawable de chaque sous-noeud <item> définit des Drawables comme des composites. Chaque Drawable sera empilé dans l'ordre de l'index, du coup, le premier du tableau sera finalement placé au bas de la pile. A titre d'exemple, je vous propose de prévoir le fond du projet de localisation avec deux couches, d'une part notre trame de fond sur laquelle se place le dégradé circulaire que nous avons déjà mis en oeuvre précédemment : <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item /> <item /> </layer-list> <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:tilemode="repeat" /> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <gradient android:startcolor="#aaff0000" // Il faut une couleur transparente pour voir la trame de fond android:endcolor="#aaffff00" // Il faut une couleur transparente pour voir la trame de fond android:type="radial" android:gradientradius="500" android:centerx="0" android:centery="1" /> res/drawable/fond.xml res/drawable/trame.xml res/drawable/gradient.xml

58 </shape> Drawables state list Un Drawable state list est une ressource composite qui vous permet de spécifier un Drawable à afficher en fonction de l'état de la View à laquelle il a été assigné. La plupart des Views Android natives utilisent des Drawables state list, comme l'image utilisée pour les boutons et le fond des items de la ListView standard. Pour définir un Drawable state list, créez un fichier XML spécifiant une ressource Drawable alternative pour chaque état.. <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_window_focused="false" /> <item android:state_pressed="true" /> <item android:state_focused="true" /> <item /> </selector> Prendre des photos Les appareils photo - qui peuvent également prendre des vidéos selon les modèles - sont maintenant présents sur la plupart des téléphones. Bien qu'ils soient nettement moins performants que des appareils classiques pour la plupart des utilisations, ils offrent néanmoins de très nombreuses fonctionnalités et représentent une source quasi inépuisable d'applications. Utiliser les intentions pour prendre des photos La façon la plus simple de prendre une photo est d'utiliser la constante statique ACTION_IMAGE_CAPTURE du MediaStore dans un Intent passé à la méthode startactivityforresult(). Ceci lancera l'activité de l'apn (Appareil Photo Numérique), permettant aux utilisateurs de modifier manuellement les réglages, et vous évitera de redévelopper entièrement une application. L'action de capture d'image supporte deux méthodes, Thumbnail (vignette) et Full Image (taille réelle) : 1. Thumbnail : Par défaut, la photo prise par l'action de capture d'image renverra une vignette en format bitmap dans l'extra "data" du paramètre Intent renvoyé par la méthode onactivityresult(). Appelez la méthode getparcelableextra() sur le paramètre Intent en spécifiant le nom de l'extra "data" pour renvoyer cette vignette. 2. Full Image : Si vous spécifiez une URI de sortie en utilisant l'extra MediaStore.EXTRA_OUTPUT dans l'intent de lancement, l'image en taille réelle sera sauvegardée à l'emplacement spécifié. Dans ce cas, aucune vignette ne sera renvoyée dans le rappel de l'activité et le "data" de l'intent sera null. L'activité suivante montre comment utiliser l'action de capture d'image pour récupérer une vignette ou une photo en taille réelle à l'aide du mécanisme des intentions : <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <Button android:text="photo mignature" android:onclick="vignette" /> <Button android:text="photo normale" android:onclick="sauvegarde" /> <ImageView android:layout_height="fill_parent" /> </LinearLayout> package fr.btsiris.photo; import android.app.activity; import android.content.intent; import android.graphics.*; import android.net.uri; import android.os.*; import android.provider.mediastore; import android.view.view; import android.widget.*; import java.io.file; res/layout/main.xml fr.btsiris.photo.photo.java

59 public class Photo extends Activity { private ImageView image; private Uri fichieruri; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); image = (ImageView) findviewbyid(r.id.image); public void vignette(view vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startactivityforresult(intention, 1); public void sauvegarde(view vue) { Intent intention = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File fichier = new File(Environment.getExternalStorageDirectory(), "test.jpg"); fichieruri = Uri.fromFile(fichier); intention.putextra(mediastore.extra_output, fichieruri); startactivityforresult(intention, 1); protected void onactivityresult(int requestcode, int resultcode, Intent data) { if (requestcode == 1) { Uri imageuri = null; if (data!= null) { if (data.hasextra("data")) { Bitmap vignette = data.getparcelableextra("data"); image.setimagebitmap(vignette); else { File fichier = new File(Environment.getExternalStorageDirectory(), "test.jpg"); Bitmap photo = BitmapFactory.decodeFile(fichier.getPath()); image.setimagebitmap(photo); Contrôler l'apn et prendre des photos Que ce soit pour prendre un cliché ou pour régler des paramètres, nous utiliserons principalement la classe Camera dans nos diverses manipulations. Cette classe Camera permet d'ajuster les différents réglages de prise de vue, de spécifier les préférences pour les images et bien entendu de prendre les photos. Pour accéder au Service Camera, utilisez la méthode statique open() de la classe Camera. Lorsque votre application a fini de l'utiliser, n'oubliez pas de la libérer en appelant la méthode release(). La méthode open() ouvrira et initialisera l'appareil photo. Vous pourrez alors modifier les réglages, configurer la surface de prévisualisation et prendre des photos comme nous allons le montrer dans les sections suivantes. N'oubliez pas également que pour utiliser l'appareil photo sous Android, il faut ajouter la permission CAMERA dans le manifeste de l'application : <uses-permission android:name="android.permission.camera" /> Contrôler et surveiller des réglages de l'apn et les options d'image Les réglages de l'apn sont stockés dans l'objet Camera.Parameters, accessible en appelant la méthode getparameters() de l'objet Camera. Tous les paramètres sont alors consultables grâce à leurs accesseurs (méthodes getxxx() et setxxx() associées). Pour consulter le format photo, par exemple, nous utiliserons la méthode getpictureformat() et pour paramétrer ce format en JPEG, nous invoquerons la méthode setpictureformat(pixelformat.jpeg). Android 2.0 (API level 5) a introduit une large gamme de paramètres d'apn, chacun possédant un getter et un setter : 1. [get / set] SceneMode : Reçoit ou renvoie une constante chaîne statique SCENE_MODE_* de la classe Camera.Parameters. Chaque mode décrit un type de scène particulier (fête, plage, soleil couchant, etc.). 2. [get / set] FashMode : Reçoit ou renvoie une constante chaîne statique FLASH_MODE_*. Permet de spécifier le mode du flash à on, off, atténuation des yeux rouges ou flashlight. 3. [get / set] WhiteBalance : Reçoit ou renvoie une constante chaîne statique WHITE_BALANCE_* décrivant la balance des blancs de la scène photographiée. 4. [get / set] ColorEffect : Reçoit ou renvoie une constante chaîne statique EFFECT_* modifiant la représentation de l'image. Les effets disponibles incluent le sepia ou le noir et blanc.

60 5. [get / set] FocusMode : Reçoit ou renvoie une constante chaîne statique FOCUS_MODE_* spécifiant comment l'autofocus doit tenter de se régler. La plupart des paramètres ci-dessus ne sont vraiment utiles que si vous remplacez l'application native. Cela dit, ils peuvent également servir à personnaliser la façon dont la prévisualisation est affichée et vous permettre de la personnaliser pour des applications de réalité augmentée. Les paramètres peuvent également servir à lire ou spécifier la taille, la qualité et le format des images, des vignettes et des prévisualisations. La liste qui suit explique comment fixer certaines de ces valeurs : 1. Qualité des JPEG et des vignettes : Utilisez les méthodes setjpegquality() et setjpegthumbnailquality() en leur passant une valeur entière entre 0 et 100, cette dernière représentant la qualité la plus élevée. 2. Taille des images, prévisualisations et vignettes : Utilisez setpicturesize(), setpreviewsize(), setjpegthumbnailsize() pour spécifier une hauteur et une largeur. 3. Format de pixel des images et des prévisualisations : Utilisez setpictureformat(), setpreviewformat() pour fixer le format des images à l'aide d'une constante statique de la classe PixelFormat. 4. Taux de trame des prévisualisations : Utilisez setpreviewframerate() pour spécifier ce taux en fps (frames per second). Chaque appareil peut potentiellement supporter un sous-ensemble différent de ces paramètres. La classe Camera.Parameters inclut également une série de méthodes getsupportedxxx() permettant de déterminer les options valides à afficher à l'utilisateur ou de vérifier qu'un paramètre particulier est supporté avant de lui assigner une valeur. Cette vérification est particulièrement importante lorsque nous sélectionnons une prévisualisation ou des tailles d'images. Voici d'ailleur un exemple qui permet de proposer des photos en sépia. Camera appareil = Camera.open(); Camera.Parameters parametres = appareil.getparameters(); List<String> effetscouleur = parametres.getsupportedcoloreffects(); if (effetscouleur.contains(camera.parameters.effect_sepia)) parametres.setcoloreffect(camera.parameters.effect_sepia); appareil.setparameters(parametres); Surveiller l'autofocus Si l'apn supporte l'autofocus et qu'il soit activé, vous pouvez surveiller le succès des opérations de focus en ajoutant un AutoFocusCallback à l'objet Camera. Le gestionnaire d'événement onautofocus() reçoit un paramètre Camera lorsque le statut de l'autofocus a changé et un booléen pour indiquer le succès ou l'échec. Camera appareil = Camera.open(); appareil.autofocus(new AutoFocusCallback() { public void onautofocus(boolean success, Camera camera) { // A faire si l'autofocus réussit ); Utiliser les prévisualisations L'accès aux flux vidéo temps réel de la caméra signifie que vous pouvez incorporer ce flux dans vos applications. Certaines des applications Android les plus excitantes utilisent cette fonctionnalité pour implémenter la réalité augmentée (processus d'ajout de données dynamiques contextuelles, comme des détails sur les monuments historiques ou des points d'intérêt, par dessus un flux vidéo en temps réel). La prévisualisation du champ filmé par la caméra peut être affichée sur une surface spécifique. Ainsi, pour voir ce flux temps réel de votre caméra au sein de votre application, vous devez inclure une balise <SurfaceView> dans votre interface utilisateur. A noter que sur un émulateur, le rendu affichera un damier sur lequel un carré bouge de façon aléatoire. Cette surface d'affichage est donc décrite dans un fichier de mise en page XML de façon assez habituelle en spécifiant sa taille. La surface que nous allons détenir en la manipulant sera contrôlable grâce à une instance d'un objet de type SurfaceHolder. L'interface SurfaceHolder implémentée par cette objet permet : 1. De contrôler le taille de la surface. 2. De changer son format. 3. D'éditer les pixels de la surface. 4. De surveiller les changements inhérents à la surface. Un client peut surveiller les événements liés à la surface en implémentant l'interface du callback SurfaceHolder.Callback. Une fois enregistré, ce client sera notifié des événements de création, suppression et modification de la surface. Implémenter donc un SurfaceHolder.Callback pour vérifier la construction d'une surface valide avant de la passer à la méthode setpreviewdisplay(). Rappel sur la gestion événementielle : Un callback, terme qui pourrait se traduire en français par "rappel", est en pratique matérialisé par un objet implémentant les méthodes d'une interface. Cette dernière invoquera les méthodes de l'interface que l'objet implémente pour signaler un fait donné lors d'un événement précis ou à la fin d'un traitement, par exemple. Je vous propose de réaliser une toute petite application qui permet de faire uniquement la prévisualisation et donc d'afficher sur l'ensemble de

61 l'écran ce que voit directement l'appareil photo : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.appareil" android:versioncode="1" android:versionname="1.0"> <application android:label="appareil photo" > <activity android:name="appareilphoto" android:label="appareil photo" android:screenorientation="landscape"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.camera" /> </manifest> AndroidManifest.xml Pensez-bien à demander l'autorisation d'utiliser l'appareil photo intégré avec la permission adaptée. Un autre détail qui a son importance, vous devez spécifier l'orientation de l'activité principale en mode paysage "landscape", sinon votre prévisualisation sera toujours montrée suivant un angle de 90. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <SurfaceView android:layout_width="wrap_content" android:layout_weight="1"/> </LinearLayout> package fr.btsiris.appareil; res/layout/main.xml fr.btsiris.appareil.appareilphoto.java import android.app.activity; import android.hardware.camera; import android.os.bundle; import android.util.log; import android.view.*; import java.io.ioexception; public class AppareilPhoto extends Activity implements SurfaceHolder.Callback { private Camera appareil; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); SurfaceView surface = (SurfaceView) findviewbyid(r.id.surface); SurfaceHolder holder = surface.getholder(); holder.addcallback(this); holder.settype(surfaceholder.surface_type_push_buffers); public void surfacecreated(surfaceholder holder) { appareil = Camera.open(); public void surfacechanged(surfaceholder holder, int format, int largeur, int hauteur) { if (appareil!=null) { try { appareil.setpreviewdisplay(holder); appareil.startpreview(); catch (IOException ex) { Log.d("Prévisualisation : ", ex.getmessage()); public void surfacedestroyed(surfaceholder holder) { if (appareil!=null) { appareil.stoppreview(); appareil.release(); 1. Dans ce code source, nous voyons que l'activité principale implémentante l'interface SurfaceHolder.Callback. Il est nécessaire de

62 s'enregistrer auprès de cette activité pour être notifié des événements relatifs à la surface. 2. C'est d'ailleurs l'objet de type SurfaceHolder obtenu au près de la surface déclarée dasn le fichier de mise en page qui nous notifiera des différentes étapes de création, suppression et modification de la surface. 3. A la création de la surface, nous récupérons l'objet permettant de manipuler la caméra. 4. Quand la surface est modifiée, nous diffusons sur la surface les images captées par l'objectif. La documentation précise bien que cette méthode est appelée au moins une fois après la création de la surafce, donc pas de souci. 5. Enfin, quand la surface est détruite, nous libérons les ressources liées à la caméra. Il est également tout à fait possible d'assigner un PreviewCallback qui sera déclanché pour chaque trame de prévisualisation, vous permettant ainsi de les manipuler individuellement. Dans ce cas là, appelez la méthode setpreviewcallback() sur l'objet Camera en lui passant une nouvelle implémentation de PreviewCallback redéfinissant la méthode onpreviewframe(). Ainsi, chaque trame sera reçue par l'événement onpreviewframe() et l'image sera passée via le tableau d'octets. Prendre une photo (capture d'une image) Pour prendre une image (une photo), nous utilisons la méthode takepicture() sur une instance de l'objet Camera. Cette méthode prend en paramètre un certain nombre de callbacks. Elle prend effectivement en compte un ShutterCallback et deux implémentations de PictureCallback (l'une pour l'image Raw et l'autre pour l'image JPEG). Chaque callback d'image recevra un tableau d'octets représentant l'image dans le format approprié, et le callback de l'obturateur (shutter) sera déclenché immédiatement après la fermeture de ce dernier. appareil.takepicture(shuttercallback, rawcallback, jpegcallback); Comme d'habitude, ces callback sont des interfaces que vous devez implémenter afin de répondre de façon circonstanciée à l'événement de la prise de photo, de la capture de votre image. Prenons l'exemple de la méthode onshutter() de l'objet qui implémente l'interface ShutterCallback. Elle sera appelée au moment où l'obturateur se refermera et que la photo sera prise. Le code obtenu dans cette méthode sera alors exécutée. Nous pourrions, par exemple, jouer un son pour signaler la prise de photo. Voici les différents callbacks qui peuvent être utilisés : 1. Camera.AutoFocusCallback : donne des informations sur le succès ou l'échec d'une opération d'autofocus. 2. Camera.ErrorCallback : rappel sur erreur du service de prise de vue. 3. Camera.PictureCallback : prise d'une photographie. 4. Camera.PreviewCallback : notification des images disponibles en prévisualisation. 5. Camera.ShutterCallback : rappel quand l'obturateur est fermé, mais les données ne sont pas encore enregistrées. L'objet ShutterCallback est un paramètre indispensable à chaque appel de la méthode permettant de prendre une photo. Vous pouvez ensuite choisir, grâce au PictureCallback, une capture au format brut, au format JPEG ou les deux. Reprenons le projet précédent qui permet d'afficher une prévisualisation de la caméra et ajoutons un bouton qui, actionné, déclenchera la prise d'une image au format JPEG. Cette image sera alors enregistrée directement sur la carte SD : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.appareil" android:versioncode="1" android:versionname="1.0"> <application android:label="appareil photo" > <activity android:name="appareilphoto" android:label="appareil photo"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.camera" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <Button android:text="clic clac" android:onclick="clicclac"/> AndroidManifest.xml res/layout/main.xml

63 <SurfaceView android:layout_height="fill_parent"/> </LinearLayout> package fr.btsiris.appareil; fr.btsiris.appareil.appareilphoto.java import android.app.activity; import android.hardware.camera; import android.hardware.camera.*; import android.os.bundle; import android.util.log; import android.view.*; import java.io.*; public class AppareilPhoto extends Activity implements SurfaceHolder.Callback, PictureCallback, ShutterCallback { private Camera appareil; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); SurfaceView surface = (SurfaceView) findviewbyid(r.id.surface); SurfaceHolder holder = surface.getholder(); holder.addcallback(this); holder.settype(surfaceholder.surface_type_push_buffers); public void clicclac(view vue) { appareil.takepicture(this, null, this); public void surfacecreated(surfaceholder holder) { appareil = Camera.open(); appareil.setdisplayorientation(90); // A partir de la version 2.2, permet d'avoir la prévisualisation dans le bon sens public void surfacechanged(surfaceholder holder, int format, int largeur, int hauteur) { if (appareil!=null) { try { appareil.setpreviewdisplay(holder); appareil.startpreview(); catch (IOException ex) { Log.d("Prévisualisation : ", ex.getmessage()); public void surfacedestroyed(surfaceholder holder) { if (appareil!=null) { appareil.stoppreview(); appareil.release(); public void onpicturetaken(byte[] image, Camera appareil) { try { FileOutputStream os = new FileOutputStream("/sdcard/test.jpg"); os.write(image); os.close(); catch (IOException ex) { Log.e("Erreur", "Impossible de stocker la photo"); appareil.startpreview(); public void onshutter() { Log.d(getClass().getSimpleName(), "Clic clac!"); Vous avez ci-dessous une alternative qui permet d'archiver votre photo dans un emplacement spécifique au stockage de médias.. package fr.btsiris.appareil; fr.btsiris.appareil.appareilphoto.java import android.app.activity; import android.content.contentvalues; import android.hardware.camera; import android.hardware.camera.*; import android.net.uri;

64 import android.os.bundle; import android.provider.mediastore.images.media; import android.util.log; import android.view.*; import java.io.*; public class AppareilPhoto extends Activity implements SurfaceHolder.Callback, PictureCallback, ShutterCallback { private Camera appareil; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); SurfaceView surface = (SurfaceView) findviewbyid(r.id.surface); SurfaceHolder holder = surface.getholder(); holder.addcallback(this); holder.settype(surfaceholder.surface_type_push_buffers); public void clicclac(view vue) { appareil.takepicture(this, null, this); public void surfacecreated(surfaceholder holder) { appareil = Camera.open(); appareil.setdisplayorientation(90); public void surfacechanged(surfaceholder holder, int format, int largeur, int hauteur) { if (appareil!=null) { try { appareil.setpreviewdisplay(holder); appareil.startpreview(); catch (IOException ex) { Log.d("Prévisualisation : ", ex.getmessage()); public void surfacedestroyed(surfaceholder holder) { if (appareil!=null) { appareil.stoppreview(); appareil.release(); public void onpicturetaken(byte[] image, Camera appareil) { ContentValues valeurs = new ContentValues(); valeurs.put(media.title, "Ma photo"); valeurs.put(media.description, "Photo prise par le téléphone"); Uri uri = getcontentresolver().insert(media.external_content_uri, valeurs); try { OutputStream os = getcontentresolver().openoutputstream(uri); os.write(image); os.close(); catch (IOException ex) { Log.e("Erreur", "Impossible de stocker la photo"); appareil.startpreview(); public void onshutter() { Log.d(getClass().getSimpleName(), "Clic clac!"); Observez la syntaxe Image.Media.EXTERNAL_CONTENT_URI. Ce morceau de code permet de récupérer l'emplacement où sont stockées les images enregistrées sur un média externe et d'y placer ses propres images. Le stockage vidéo et audio fonctionne exactement de la même façon. Lire et écrire des images au format JPEG EXIF La classe ExifInterface fournit les mécanismes nécessaires à la lecture et à la modification des données au format EXIF (Exchangeable Image File Format) stockées dans un fichier JPEG. Créez une instance d'exifinterface en passant en paramètre le nom du fichier complet. ExifInterface exif = new ExifInterface(nomFichier); Les données EXIF sont utilisées pour stocker une large gamme de métadonnées relatives à des photographies comme la date et l'heure, les paramètres de l'apn (marque et modèle) et les réglages de l'image (ouverture et vitesse d'obturation) ainsi que des description et des emplacements. Pour lire un attribut EXIF, appelez la méthode getattribute() sur l'objet ExifInterface en lui passant le nom de l'attribut à lire. La classe ExifInterface contient plusieurs constantes statiques TAG_* qui peuvent être utilisées pour accéder aux métadonnées EXIF

65 courantes. Pour modifier un attribut EXIF, utilisez la méthode setattribute() en lui passant le nom de l'attribut et sa nouvelle valeur. 1. TAG_APERTURE : Ouverture de l'objectif. 2. TAG_DATETIME : L'heure et la date de la prise de vue. 3. TAG_EXPOSURE_TIME : Temps d'exposition. 4. TAG_FLASH : Flash actif ou pas. 5. TAG_FOCAL_LENGTH : Longueur focale de l'objectif. 6. TAG_GPS_ALTITUDE : Altitude relative en référence à la constante TAG_GPS_ALTITUDE_REF. 7. TAG_GPS_ALTITUDE_REF : Altitude de référence, normalement à 0 lorsque nous sommes au niveau de la mer. 8. TAG_GPS_DATESTAMP : Date donnée par le GPS. 9. TAG_GPS_LATITUDE : Latitude relative en référence à la constante TAG_GPS_LATITUDE_REF. 10. TAG_GPS_LATITUDE_REF: Latitude de référence. 11. TAG_GPS_LONGITUDE : Longitude relative en référence à la constante TAG_GPS_LONGITUDE_REF. 12. TAG_GPS_LONGITUDE_REF : Longitude de référence. 13. TAG_GPS_TIMESTAMP : Heure donnée par le GPS. 14. TAG_IMAGE_LENGTH : Poids de l'image. 15. TAG_IMAGE_WIDTH : Largeur de l'image. 16. TAG_ISO : Sensibilité utilisée lors de la prise de vue. 17. TAG_MAKE : Nom du constructeur. 18. TAG_MODEL : Modèle de l'appareil photo. 19. TAG_ORIENTATION : Orientation de la prise de vue. 20. TAG_WHITE_BALANCE : Balance des blancs choisie avant de prendre la photo. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <TextView android:text="poids : "/> <TextView android:text="largeur : "/> <Button android:text="clic clac" android:onclick="clicclac"/> <SurfaceView android:layout_height="fill_parent"/> </LinearLayout> package fr.btsiris.appareil; res/layout/main.xml fr.btsiris.appareil.appareilphoto.java import android.app.activity; import android.hardware.camera; import android.hardware.camera.*; import android.media.exifinterface; import android.os.bundle; import android.util.log; import android.view.*; import android.widget.textview; import java.io.*; public class AppareilPhoto extends Activity implements SurfaceHolder.Callback, PictureCallback, ShutterCallback { private Camera appareil; private TextView poids, largeur; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main);

66 SurfaceView surface = (SurfaceView) findviewbyid(r.id.surface); poids = (TextView) findviewbyid(r.id.poids); largeur = (TextView) findviewbyid(r.id.largeur); SurfaceHolder holder = surface.getholder(); holder.addcallback(this); holder.settype(surfaceholder.surface_type_push_buffers); public void clicclac(view vue) { appareil.takepicture(this, null, this); public void surfacecreated(surfaceholder holder) { appareil = Camera.open(); appareil.setdisplayorientation(90); public void surfacechanged(surfaceholder holder, int format, int largeur, int hauteur) { if (appareil!=null) { try { appareil.setpreviewdisplay(holder); appareil.startpreview(); catch (IOException ex) { Log.d("Prévisualisation : ", ex.getmessage()); public void surfacedestroyed(surfaceholder holder) { if (appareil!=null) { appareil.stoppreview(); appareil.release(); public void onpicturetaken(byte[] image, Camera appareil) { try { FileOutputStream os = new FileOutputStream("/sdcard/test.jpg"); os.write(image); os.close(); ExifInterface exif = new ExifInterface("/sdcard/test.jpg"); poids.settext("poids : "+exif.getattribute(exifinterface.tag_image_length)); largeur.settext("largeur : "+exif.getattribute(exifinterface.tag_image_width)); catch (IOException ex) { Log.e("Erreur", "Impossible de stocker la photo"); appareil.startpreview(); public void onshutter() { Log.d(getClass().getSimpleName(), "Clic clac!"); Bluetooth Le Bluetooth est une technologie de communication radio de courte distance, créée pour simplifier les connexions entre appareils. Ce système a été conçu dans le but de remplacer les câbles des imprimantes, des kits "main libre", des souris/claviers, téléphones portables/pda, etc. La plate-forme Android supporte le Bluetooth afin de permettre à des appareils d'échanger des données entre elles. Afin de pouvoir profiter de ce système de connexion, Android apporte un ensemble d'api que nous allons détailler dans ce chapitre, et qui vont nous permettre d'interagir avec l'adaptateur Bluetooth local et à communiquer avec des périphériques distants. Avec Bluetooth, vous pouvez rechercher des périphériques à portée de l'appareil et vous y connecter. En initiant une communication avec les Sockets Bluetooth, vous pouvez transmettre et recevoir des flux de données depuis et vers des périphériques, à partir de votre application. Les bibliothèques Bluetooth ne sont disponibles que depuis Android 2.0 (SDK API Level 5). Il est également important de noter que tous les appareils Android n'incluent pas nécessairement un matériel Bluetooth. Bluetooth est un protocole de communication conçu pour des communications peer-to-peer (appareil à appareil) de courtes distantes et à faible bande passante. Sous Android 2.1, seules les communications cryptées sont supportées, ce qui signifie que vous ne pouvez connecter que des périphériques préalablement appariés. Les périphériques et connexions Bluetooth sont gérés par les classes suivantes : 1. BluetoothAdapter : Représente l'adaptateur local, c'est-à-dire l'appareil Android sur lequel votre application est exécuté. Il s'agit de la classe centrale pour les interactions avec le Bluetooth, elle est utilisée pour récupérer les appareils environnants, effectuer toutes les requêtes et communiquer avec les autres appareils. 2. BluetoothDevice : Représente chaque périphérique distant avec lequel vous voulez communiquer. Elle est utilisée pour récupérer les informations et créer une connexion avec l'appareil distant. 3. BluetoothSocket : Représente un point de connexion client en relation avec un service Bluetooth identifié par la classe ci-dessous. Appelez la

67 méthode createrfcommsockettoservicerecord() sur un objet BluetoothDevice pour créer un BluetoothSocket qui vous permettra d'effectuer une demande de connxion vers le périphérique distant puis d'initier la communication. 4. BluetoothServerSocket : Mise en oeuvre d'un service de connexion Bluetooth qui sera en attente de l'établissement d'une communication avec un client, soumettant ensuite la requête désirée. En créant un BluetoothServerSocket, en utilisant la méthode listenusingrfcommwithservicerecord() sur votre BluetoothAdapter local, vous pouvez écouter les demandes de connexions provenant des BluetoothSocket des périphériques distants. Ces API supportent la connexion à un appareil dans une configuration point à point (l'appareil est connecté à un seul autre appareil) ou alors en multi-points (l'appareil peut être connecté à plusieurs appareils simultanément. Les permissions A l'instar de toutes les fonctionnalités de communication, pour qu'une application puisse utiliser les capacités Bluetooth, vous devrez définir une ou deux permissions adaptées dans le manifeste : <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> 1. BLUETOOTH : Cette permission autorise votre application à accepter une connexion entrante, à demander une connexion distante et à transférer des données. Elle permet également d'initier la découverte des périphériques asservis. 2. BLUETOOTH_ADMIN : Cette permission permet d'activer la recherche des autres appareils ou de manipuler et modifier les paramètres Bluetooth du téléphone. Accéder à l'adaptateur Bluetooth local Avant de pouvoir utiliser le Bluetooth et de pouvoir échanger avec d'autres appareils, vous devez vérifier que l'appareil de l'utilisateur possède bien la fonctionnalité du Bluetooth et si celle-ci est activée. Obtention de l'adaptateur Bluetooth local L'objet BluetoothAdapter est incontournable pour l'utilisation du Bluetooth sur la plate-forme Android. La première chose que vous aurez donc à réaliser dans votre code sera de récupérer une instance de cette classe. Vous réaliserez cette opération via la méthode statique getdefaultadapter() qui vous renverra l'objet en question et qui vous servira à vérifier l'existence de l'activation du Bluetooth sur le téléphone de l'utilisateur. Si la méthode renvoie un objet null alors le téléphone de l'utilisateur ne possède pas le Bluetooth et vous pourrez désactiver toutes les fonctionnalités utilisant ce service de communication. Vérification de l'activation du Bluetooth sur le mobile de l'utilisateur Si le Bluetooth est disponible sur l'appareil, vous devez également vérifier que celui-ci est activé avant d'essayer de l'utiliser. Pour cela, vous bénéficiez de la méthode isenabled() de la classe BluetoothAdapter. Si la méthode retourne false, alors le Bluetooth n'est pas activé. Lecture des propriétés de l'adaptateur local Si l'adaptateur est activé et que vous ayez ajouté la permission BLUETOOTH à votre manifest, vous pouvez accéder à son nom (une chaîne arbitraire que les utilisateurs peuvent modifier puis utiliser pour identifier un périphérique particulier) et à son adresse matérielle (adresse MAC). Réglage des propriétés de l'adaptateur local Si vous avez également la permision BLUETOOTH_ADMIN, vous pouvez modifier le nom de l'adaptateur eu utilisant la méthode setname(). Pour obtenir une description plus détaillée de l'état courant de l'adaptateur, utilisez la méthode getstate() qui renvoie l'une des constantes de BluetoothAdapter suivantes : STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, STATE_OFF. Activation de l'adaptateur local Par défaut, l'adaptateur Bluetooth est désactivé. Pour économiser leur batterie et optimiser la sécurité, beaucoup d'utilisateurs le laisseront ainsi. Vous pouvez demander à l'utilisateur de l'activer en démarrant une sous-activité du système, spécialement conçue à cet effet, en utilisant la constante statique ACTION_REQUEST_ENABLE de la classe BluetoothAdapter et en spécifiant un Intent à l'aide de cette constante comme action de startactivityforresult(). Elle informe l'utilisateur que son Bluetooth va être et lui demande confirmation. Si l'utilisateur accepte, la sous-activité sera fermée et reviendra à l'activité appelante une fois l'adaptateur activé (ou si une erreur survient). Si l'utilisateur refuse, la sous-activité sera simplement fermée. Utilisez le code de retour du gestionnaire onactivityresult() pour déterminer le succès de l'opération. Afin de valider cette première partie, je vous propose de réaliser une petite expérience qui nous permettra de connaître l'identification de votre adaptateur local en demandant l'activation du Bluetooth si ce dernier n'est pas encore actif : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="test Bluetooth" > <activity android:name="bluetooth" android:label="identification Bluetooth!"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.bluetooth" /> </manifest> AndroidManifest.xml

68 package fr.btsiris.bluetooth; fr.btsiris.bluetooth.bluetooth.java import android.app.activity; import android.bluetooth.bluetoothadapter; import android.content.intent; import android.os.bundle; import android.widget.toast; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); if (adaptateurlocal!= null) if (adaptateurlocal.isenabled()) { String texte = adaptateurlocal.getname() + " : " + adaptateurlocal.getaddress(); Toast.makeText(this, texte, Toast.LENGTH_LONG).show(); else { Intent intention = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startactivityforresult(intention, 0); else finish(); protected void onactivityresult(int requestcode, int resultcode, Intent data) { String texte=""; switch (resultcode) { case RESULT_OK : texte = adaptateurlocal.getname() + " : " + adaptateurlocal.getaddress(); break; case RESULT_CANCELED : texte = "Bluetooth inactif : sortie de l'activité"; finish(); break; Toast.makeText(this, texte, Toast.LENGTH_LONG).show(); Il est également possible d'activer ou de désactiver directement l'adaptateur en utilisant les méthodes enabled() et disable() si vous ajouter la permission BLUETOOTH_ADMIN dans votre manifeste. Notez que ceci ne doit être fait que lorsque c'est absolument nécessaire et que l'utilisateur doit toujours être averti si vous modifiez le statut de l'adaptateur. Dans la plupart des cas, vous devrez utiliser le mécanisme d'intention décrit plus haut. Activer et désactiver l'adaptateur Bluetooth sont des opérations asynchrones parfois longues. Plutôt que sonder l'adaptateur, votre application devra enregistrer un Broacast Receiver écoutant l'action ACTION_STATE_CHANGED. Le Broadcast Intent inclura deux extras, EXTRA_STATE et EXTRA_PREVIOUS_STATE, qui indiquerons l'état courant et précédent de l'adaptateur. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="test Bluetooth" > <activity android:name="bluetooth" android:label="identification Bluetooth!"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> </manifest> package fr.btsiris.bluetooth; AndroidManifest.xml fr.btsiris.bluetooth.bluetooth.java import android.app.activity; import android.bluetooth.bluetoothadapter; import android.content.*; import android.os.bundle; import android.widget.toast; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal;

69 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); registerreceiver(new ReceptionBluetooth(), new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); if (adaptateurlocal!= null) { if (!adaptateurlocal.isenabled()) adaptateurlocal.enable(); else finish(); class ReceptionBluetooth extends BroadcastReceiver { public void onreceive(context cntxt, Intent intent) { int etat = intent.getintextra(bluetoothadapter.extra_state, -1); String texte=""; switch (etat) { case BluetoothAdapter.STATE_TURNING_ON : texte = "Bluetooth en cours d'activation"; break; case BluetoothAdapter.STATE_ON : texte = adaptateurlocal.getname()+" : "+adaptateurlocal.getaddress(); break; case BluetoothAdapter.STATE_TURNING_OFF : texte = "Bluetooth en cours de désactivation"; break; case BluetoothAdapter.STATE_OFF : texte = "Bluetooth actuellement inactif"; break; Toast.makeText(Bluetooth.this, texte, Toast.LENGTH_LONG).show(); Rechercher d'autres appareils Bluetooth - Découverte de périphériques Le processus permettant à deux périphériques de se trouver pour se connecter est appelé découverte. Avant que vous ne puissiez établir un Bluetooth Socket pour communiquer, l'adaptateur local doit être apparié au périphérique distant. Et avant que cette liaison ne puisse être effectuée les deux périphériques doivent d'abord se découvrir. Bien que le protocole Bluetooth supporte les connexion ad hoc pour le transfert de données, ce mécanisme n'est pas actuellement disponible sous Android. Les communications Bluetooth sous Android ne sont supportées qu'entre deux périphériques appariés. Vous pouvez rechercher les appareils Bluetooth disponibles autour de l'utilisateur. Cependant, pour qu'un appareil puisse être découvert, il faut que celui-ci autorise sa découverte. Si un appareil autorise sa découverte, une association sera alors établie et les appareils échangeront leurs informations d'identification (nom de l'appareil, son adresse MAC, etc). Une association signifie que les deux appareils sont conscients de leur existence respective, qu'ils sont capables de s'authentifier l'un l'autre et d'initier une connexion sécurisée entre eux. Adresse Mac : En réseau informatique, une adresse MAC (Media Access Control) est un identifiant physique stocké dans une carte réseau, attribué de façon unique à chaque carte à l'échelle mondiale : cet identifiant est en partie constitué par un champs identifiant le fabriquant de la carte et l'autre partie attribuée par le constructeur lui-même. Une fois l'association établie avec un appareil distant, vous pouvez vous connecter à celui-ci en utilisant son adresse MAC pour transférer des données via un flux RFCOMM. Android obligeant un appareil à être associé avant de pouvoir s'y connecter, tous les paramètres des appareils qui ont été associés sont enregistrés pour un usage ultérieur et ainsi éviter de réaliser systématiquement une détection des appareils. Protocole RFCOMM : Le protocole FRCOMM émule les paramètres de la ligne série câblée ainsi que le statut d'un port série RS-232. Il est utilisé pour permettre le transfert des données série. Ce protocole est notamment utilisé par les modems, les imprimantes et les ordinateurs. Le Bluetooth utilise ce protocole pour échanger des données. Gérer la visibilté de l'adaptateur Pour que des appareils distants puissent trouver votre adaptateur local durant un scan, celui-ci doit être visible. Pour des raisons de confidentialité, les appareils Android ne sont pas visibles par défaut. La visibilté de l'adaptateur Android est indiquée par son mode de scan. Vous pouvez trouver celui-ci en appelant la méthode getscanmode() de l'objet BluetoothAdapter. Elle renverra l'une des contantes suivantes : 1. SCAN_MODE_CONNECTABLE_DISCOVERABLE : L'appareil est visible par tout périphérique Bluetooth effectuant un scan de découverte. 2. SCAN_MODE_CONNECTABLE : Les périphériques Bluetooth ayant déjà découvert l'appareil pourront s'y connecter, mais pas les nouveaux. 3. SCAN_MODE_NONE : La découverte est désactivée. Si vous souhaitez rendre accessible l'appareil de l'utilisateur lors d'une recherche d'appareils Bluetooth, vous devez demander l'autorisation à l'utilisateur. Pour cela, lancez l'activité adéquate en spécifiant une intention associé à l'action ACTION_REQUEST_DISCOVERABLE. Par défaut, la visibilité est activée durant 2 minutes. Vous pouvez modifier ce réglage en ajoutant un extra EXTRA_DISCOVERABLE_DURATION au lancement de l'intention et en spécifiant le nombre de secondes de visibilité souhaitées.

70 Lorsque l'intention est diffusée, l'utilisateur se voit demander l'autorisation de rendre l'appareil visible pour la durée spécifiée. Pour savoir si l'utilisateur a accepté ou rejeté la demande, redéfinissez le gestionnaire onactivityresult(). Le paramètre resultcode renvoyé indique la durée de visibilité ou un nombre négatif si l'utilisateur à refusé. De façon alternative, vous pouvez monitorer les changements de visibilité en recevant une action ACTION_SCAN_MODE_CHANGED. Le Broadcast Intent inclut les modes de scan en cours et précédents en extras. Je propose la même démarche que précédemment en demandant à l'utilisateur l'autorisation de rendre son mobile disponible pour la communication Bluetooth. Si l'adaptateur n'est pas actif, le système le mettra en fonction au moment de la validation : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="test Bluetooth" > <activity android:name="bluetooth" android:label="identification Bluetooth!"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.bluetooth" /> </manifest> package fr.btsiris.bluetooth; import android.app.activity; import android.bluetooth.bluetoothadapter; import android.content.*; import android.os.bundle; import android.widget.toast; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; AndroidManifest.xml fr.btsiris.bluetooth.bluetooth.java public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); registerreceiver(new ReceptionBluetooth(), new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)); startactivityforresult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 0); class ReceptionBluetooth extends BroadcastReceiver { public void onreceive(context cntxt, Intent intent) { String texte=""; switch(intent.getintextra(bluetoothadapter.extra_scan_mode, -1)) { case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE : texte = "Appareil visible"; break; case BluetoothAdapter.SCAN_MODE_CONNECTABLE : texte = "Appareil déjà visible"; break; case BluetoothAdapter.SCAN_MODE_NONE : texte = "Appareil non visible"; break; Toast.makeText(Bluetooth.this, texte, Toast.LENGTH_LONG).show(); Récupérer les appareils associés Au lieu de lancer immédiatement une nouvelle recherche d'appareils Bluetooth, il convient dans un premier temps de rechercher si l'appareil de l'utilisateur ne connaît pas déjà l'appareil ciblé. Pour cela, vous allez demander à la méthode getbondeddevices() d'effectuer une requête sur les appareils déjà connus. BluetoothAdapter adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> appareils = adaptateurlocal.getbondeddevices(); if (appareils.size() > 0) { for (BluetoothDevice appareil : appareils) { String msg = appareil.getname() + " - MAC : " + appareil.getaddress();... Avant de pouvoir communiquer avec un appareil, vous devez obtenir l'adresse de l'appareil distant en récupérant les informations des appareils associés.

71 Rechercher les appareils environnant Nous allons voir maintenant comment initier une découverte depuis votre adaptateur local pour trouver des périphériques à proximité. Vous pouvez vérifier si l'adaptateur local est déjà en train d'effectuer une découverte en utilisant la méthode isdiscovering() de la classe BluetoothAdapter. Pour lancer une découverte des appareils, utilisez la méthode startdiscovery() de la classe BluetoothAdapter. Cette méthode retourne immédiatement une valeur booléenne indiquant si la découverte s'est correctement lancée. Pour récupérer le résultat de la recherche (qui peut prendre une dizaine de secondes), vous utiliserez un récepteur de diffusion. Pour annuler une découverte en cours, appelez la méthode canceldiscovery(). Le processus est asynchrone. Android utilise des Broadcast Intents pour vous notifier de son démarrage et de sa fin ainsi que des périphériques distants découvert durant le scan. Vous pouvez monitorer les changements dans le processus en créant des Broadcast Receivers écoutant les intentions ACTION_DISCOVERY_STARTED et ACTION_DISCOVERY_FINISHED. Les périphériques découverts sont renvoyés par le Broadcast Intents par le biais de l'action ACTION_FOUND. Chaque Broadcast Intent inclut le nom du périphérique distant dans un extra BluetoothDevice.EXTRA_NAME ainsi qu'une représentation inchangeable du périphérique sous forme d'un objet BluetoothDevice parcelable dans l'extra BluetoothDevice.EXTRA_DEVICE. L'objet BluetoothDevice renvoyé par la découverte représente le périphérique distant découvert. Il sera ensuite utilisé pour créer une connexion avec l'apatateur local, lier les appareils et transférer des données. Nous allons reprendre notre petite application de test Bluetooth dans laquelle nous allons rechercher les appareils distants. Pour cela au préalable, nous rendons notre appareil visible afin de permettre la communication ultérieure entre les deux éléments. Nous en profitons pour visualier toutes les différentes phases de mise en place : <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="test Bluetooth" > <activity android:name="bluetooth" android:label="identification Bluetooth!"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> </manifest> package fr.btsiris.bluetooth; import android.app.activity; import android.bluetooth.*; import android.content.*; import android.os.bundle; import android.widget.toast; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; private BluetoothDevice appareildistant; private String depart = BluetoothAdapter.ACTION_DISCOVERY_STARTED; private String fin = BluetoothAdapter.ACTION_DISCOVERY_FINISHED; private String trouve = BluetoothDevice.ACTION_FOUND; private String visibilite = BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; private BroadcastReceiver monitorerdecouverte = new BroadcastReceiver() { public void onreceive(context cntxt, Intent intent) { String action = intent.getaction(); if (visibilite.equals(action)) { switch(intent.getintextra(bluetoothadapter.extra_scan_mode, -1)) { case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE : case BluetoothAdapter.SCAN_MODE_CONNECTABLE : if (!adaptateurlocal.isdiscovering()) adaptateurlocal.startdiscovery(); break; case BluetoothAdapter.SCAN_MODE_NONE : Toast.makeText(Bluetooth.this, "Appareil non visible...", Toast.LENGTH_LONG).show(); break; else if (depart.equals(action)) Toast.makeText(Bluetooth.this, "Découverte en cours...", Toast.LENGTH_LONG).show(); else if (fin.equals(action)) Toast.makeText(Bluetooth.this, "Découverte terminée...", Toast.LENGTH_LONG).show(); else if (trouve.equals(action)) { appareildistant = intent.getparcelableextra(bluetoothdevice.extra_device); AndroidManifest.xml fr.btsiris.bluetooth.bluetooth.java

72 String nomappareil = intent.getstringextra(bluetoothdevice.extra_name); Toast.makeText(Bluetooth.this, "Trouvé : "+nomappareil, Toast.LENGTH_LONG).show(); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); registerreceiver(monitorerdecouverte, new IntentFilter(depart)); registerreceiver(monitorerdecouverte, new IntentFilter(fin)); registerreceiver(monitorerdecouverte, new IntentFilter(trouve)); registerreceiver(monitorerdecouverte, new IntentFilter(visibilite)); protected void onstart() { super.onstart(); startactivityforresult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 0); protected void onstop() { super.onstop(); adaptateurlocal.disable(); Je vous propose une version plus réduite qui permet de lancer la visibilité de l'appareil de l'utilisateur et la recherche de l'appareil distant en récupérant en plus son adresse MAC. package fr.btsiris.bluetooth; fr.btsiris.bluetooth.bluetooth.java import android.app.activity; import android.bluetooth.*; import android.content.*; import android.os.bundle; import android.widget.toast; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; private BluetoothDevice appareildistant; private String trouve = BluetoothDevice.ACTION_FOUND; private String visibilite = BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; private BroadcastReceiver monitorerdecouverte = new BroadcastReceiver() { public void onreceive(context cntxt, Intent intent) { String action = intent.getaction(); if (visibilite.equals(action)) { if (!adaptateurlocal.isdiscovering()) adaptateurlocal.startdiscovery(); else if (trouve.equals(action)) { appareildistant = intent.getparcelableextra(bluetoothdevice.extra_device); Toast.makeText(Bluetooth.this, appareildistant.getname()+" : "+appareildistant.getaddress(), Toast.LENGTH_LONG).show(); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); registerreceiver(monitorerdecouverte, new IntentFilter(trouve)); registerreceiver(monitorerdecouverte, new IntentFilter(visibilite)); protected void onstart() { super.onstart(); startactivityforresult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 0); protected void onstop() { super.onstop(); adaptateurlocal.disable(); unregisterreceiver(monitorerdecouverte); finish();

73 Communication entre appareils Bluetooth Pour établir une connexion entre deux appareils, vous devez impémenter chaque côté de votre logique de communication : le côté serveur et le côté client. Le client et le serveur sont considérés connectés lorsque chaque appareil possède un BluetoothSocket sur le même flux RFCOMM. C'est à ce moment que les appareils peuvent transmettre des données dans un sens ou dans l'autre. Qu'il s'agissse du client ou du serveur, l'objet BluetoothSocket sera récupéré de façon différente. Pour le serveur, cet objet lui sera renvoyé lorqu'il aura accepté la demande de connexion alors que le client l'obtiendra après l'acceptation de la demande par le seveur et l'établissement du flux RFCOMM. Protocole RFCOMM : Le protocole FRCOMM émule les paramètres de la ligne série câblée ainsi que le statut d'un port série RS-232. Il est utilisé pour permettre le transfert des données série. Ce protocole est notamment utilisé par les modems, les imprimantes et les ordinateurs. Le Bluetooth utilise ce protocole pour échanger des données. Avant que votre apllication ne puisse communiquer, les appareils doivent être appariés (liés). Si deux périphériques doivent être appariés, l'utilisateur doit explicitement l'autoriser, soit par des réglages Bluetooth, soit lorsque votre application lui demandera au moment de la tentative de connexion. Vous pouvez établir un canal de communication RFCOMM pour des communications bidirectionnelles en utilisant les classes suivantes : 1. BluetoothServerSocket : Utilisée pour établir une socket d'écoute pour initier un lien entre périphériques. Pour établir une reconnaissance (handshake), l'un des périphériques agit en tant que serveur qui écoute et accepte les demandes entrantes. 2. BluetoothSocket : Utilisée pour créer une nouvelle socket client à connecter à un serveur qui la renverra une une fois la connexion établie. Cela fait, les sockets sont utilisées par le serveur et le client pour transmettre les flux de données. Lorsque vous créez une application qui utilise Bluetooth comme couche de transport, vous devez implémenter à la fois une socket côté serveur écoutant les demandes de connexion et une socket pour initier un nouveau canal et gérer les communications. Une fois connectée, la socket serveur renvoie une socket Bluetooth utilisée ensuite pour envoyer et recevoir les données. Cette socket serveur Bluetooth est utilisée exactement de la même façon que la socket client. Les désignations serveur et client ne sont significatives que quant à la façon dont la connexion est établie. Elles n'affectent pas la circulation des flux une fois la connexion établie. Créer un service Bluetooth Une socket serveur est utilisée pour les demandes de connexion entrantes provenant des périphériques Bluetooth. Pour que deux appareils Bluetooth soient connectés, l'un doit agir comme serveur (écoutant et acceptant les demandes) et l'autre, comme client (initiant la demande de connexion au serveur). Une fois les deux appareils connectés, les communications sont gérés des deux côtés par une socket. Pour connecter deux appareils, l'un doit donc faire office de serveur ou ouvrant une socket BluetoothServerSocket qui écoutera les requêtes entrantes. Si la requête est acceptée alors un objet BluetoothSocket connecté à l'autre appareil sera renvoyé. Pour créer une socket serveur et accepter la connexion de l'appareil distant, vous devez : 1. Obtenir un objet BlutoothServerSocket en appelant la méthode listenusingrfcommwithservicerecord(string, UUID). Le premier paramètre est la chaîne représentant le nom du service. Le second paramètre est un identifiant unique qui identifie sans ambiguïté votre service. Lors de la demande de connexion, le client enverra également un identifiant unique qui devra être le même que celui spécifié ici pour que la connexion s'établisse. 2. Débuter l'écoute des demandes de connexions en appelant la méthode accept(). Cette méthode ne retourne pas immédiatement et bloque l'exécution de votre application. La méthode retournera un objet BluetoothSocket dès qu'une demande de connexion sera acceptée avec un UUID correct. Vous pourrez utiliser cet objet pour transférer vos données. Si une demande de connexion est faite par un périphérique distant n'ayant jamais été apparié à l'adaptateur local, l'utilisateur se verra demander d'accepter l'appariement avant que l'appel à accept() ne renvoie un résultat. Cette demande est faite via une notification. 3. Vous pouvez annuler l'écoute des demandes de connexions en appelant la méthode close() du BluetoothServerSocket. Dans tous les cas, appelez cette méthode pour libérer les ressources dès que vous n'aurez plus besoin de cet objet. Du fait de la nature bloquante de la méthode accept(), vous devez toujours exécuter cette méthode hors du thread principal de l'interface au risuqe de bloquer complètement votre application. Il est nécessaire de créer un fil d'exécution à part qui se charge de la demande de connexion afin de ne pas bloquer l'exécution de l'application. UUID : Un identifiant UUID est un identifiant unique (Universally Unique Identifier) représenté par une chaîne de 128 bits qui identifie l'information de façon unique. L'intérêt de l'uuid est qu'il est suffisamment grand pour que vous puissiez en générer un aléatoirement et avoir peu de chance d'entrer en collision avec le même identifiant. Trouver un périphérique Bluetooth auquel se connecter Il existe de nombreuses façons d'obtenir une référence à un périphérique Bluetooth distant et également quelques points importants à garder en tête, concernant les périphériques avec lesquels vous pouvez rentrer en communication. Pour pouvoir établir une connexion à un périphérique distant, les conditions suivantes doivent être réunies : 1. Le périphérique distant doit être visible. 2. Le périphérique distant doit accepter les connexions à l'aide d'une socket serveur.

74 3. Les deux périphériques doivent être appariés (ou liés). Si tel n'est pas le cas, l'utilisateur se verra demander de le faire au moment de la demande de connexion. Chaque objet Bluetooth représente un périphérique distant. Ces objets sont utilisés pour obtenir les propriétés des périphériques et pour initier les connexions. Il y a plusieurs façons d'obtenir un objet BluetoothDevice dans votre code. Dans chaque cas, vous devez vérifier que le périphérique auquel vous vouslez vous conencter est visible et (optionnellement) déterminer si vous y êtes liés. Si vous ne pouvez pas découvrir le périphérique distant, vous devez demander à l'utilisateur d'activer la visibilité. 1. Vous avez appris une technique pour découvrir les périphériques visibles plus haut dans cette section à l'aide de la méthode startdiscovery() et les actions de monitoring ACTION_FOUND. Vous avez appris que chaque Broadcast reçu incluait un extra BluetoothDevice.EXTRA_DEVICE contenant le périphérique découvert. 2. Vous pouvez également utiliser la méthode getremotedevice() sur l'adaptateur local en spécifiant l'adresse matérielle du périphérique distant auquel vous voulez vous connecter. BluetoothAdapter adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice appareildistant = adaptateurlocal.getremotedevice("01:23:77:35:2f:aa"); 3. Pour trouver les périphériques appariés, appelez la méthode getbondeddevices() sur l'appareil local. Vous pouvez interroger l'ensemble renvoyé pour déterminer si un périphérique cible est aparié à l'adaptateur local. BluetoothAdapter adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> appareils = adaptateurlocal.getbondeddevices(); if (appareils.contains(appareildistant) > 0) {... Créer un client Bluetooth La classe BluetoothSocket est utilisée sur le client pour initier un canal de communication depuis votre application vers un socket serveur à l'écoute. Avant de pouvoir demander une connexion à un appareil distant, vous devez posséder l'objet BluetoothDevice de ce dernier (soit en parcourant les appareils liés, soit en effectuant une nouvelle détection d'appareils). La création d'un client suit la logique suivante : 1. Appelez la méthode createrfcommsockettoservicerecord() de votre objet BluetoothDevice en spécifiant l'uuid du service désiré (le même que celui que vous aurez spécifié au BluetoothServerSocket). Cette méthode vous retourne un objet BluetoothSocket qui vous permettra de vous connecter à l'appareil plus tard. L'objet BluetoothDevice représente le serveur distant cible. Il doit avoir une socket serveur à l'écoute des demandes de connexion (comme nous l'avons décrit plus haut). 2. Appelez la méthode connect() de votre instance BluetoothSocket. cet appel créera la connexion de communication RFCOMM avec l'autre appareil si le service distant accepte votre demande. Cette méthode est bloquante ; par conséquent placez toujours votre code dans un fil d'exécution indépendant. Si la méthode connect() est trop longue (par exemple lorsque l'appareil n'est pas suffisamment près ou si une recherche d'appareils Bluetooth est en train de s'exécuter en même temps), une exception sera lancée. 3. Une fois connecté, vous pouvez vous déconnecter en appelant la méthode close() de l'objet BluetoothSocket. Dans tous les cas appelez cette méthode pour libérer les resources. Transmettre les données à l'aide des sockets Bluetooth Une fois le serveur et le client connectés, chacun va pouvoir échanger avec l'autre au travers de son objet BluetoothSocket. Effectivement, une fois la connexion établie, vous obtenez une socket sur chaque périphérique. A partir de maintenant, il n'y aura plus de distinction significative entre les deux : vous pouvez envoyer et recevoir des données en utilisant chacune des sockets. Comme pour toute communication par les sockets, les méthodes et techniques sont les mêmes. Vous pouvez utiliser les méthodes getinputstream() et getoutputstream() pour récupérer les flux entrants et sortants. Une fois les flux récupérés, vous pourrez utiliser les méthodes read() et write() respectivement pour lire et écrire des données. Ces méthodes étant bloquantes, vous devrez toujours les exécuter dans un fil d'exécution séparé. Afin de valider toute ce chapitre concernant le bluetooth, je vous propose de nous servir du mobile pour piloter les robots LEGO NXT MINDSTORM uniquement par commande bluetooth. Dans ce premier exemple, le robot doit déjà être apparié à votre mobile pour que la communication se fasse correctement. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="bluetooth NXT" > <activity android:name="bluetooth" android:label="robot NXT" android:screenorientation="portrait"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> AndroidManifest.xml

75 </intent-filter> </activity> </application> <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" > <EditText android:gravity="center" android:text="00:16:53:0c:bc:bf" /> <Button android:text="connexion avec ce robot" android:onclick="recherche" /> <Button android:layout_width="120dp" android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#0000ff" android:text="avancer" android:onclick="avancer" /> <RelativeLayout > <Button android:layout_width="120dp" android:layout_marginleft="10dp" android:layout_alignparentleft="true" android:textcolor="#0000ff" android:text="gauche" android:onclick="gauche" /> <Button android:layout_width="120dp" android:layout_marginright="10dp" android:layout_alignparentright="true" android:textcolor="#0000ff" android:text="droite" android:onclick="droite" /> </RelativeLayout> <Button android:layout_width="120dp" android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#0000ff" android:text="reculer" android:onclick="reculer" /> <Button android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#ff0000" android:text="arrêter" android:onclick="arreter" /> <LinearLayout android:padding="10dp"> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" android:text="40" android:onclick="quarante" /> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" src/layout/main.xml

76 android:text="60" android:onclick="soixante" /> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" android:text="80" android:onclick="quatrevingt" /> </LinearLayout> </LinearLayout> package fr.btsiris.bluetooth; fr.btsiris.bluetooth.bluetooth.java import android.app.activity; import android.bluetooth.*; import android.content.*; import android.os.*; import android.view.view; import android.widget.*; import java.io.*; import java.util.uuid; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; private BluetoothDevice NXT; private BluetoothSocket robot; private InputStream entree; private OutputStream sortie; private EditText adresse; private boolean connecte = false; private UUID uuidnxt = UUID.fromString(" F9B34FB"); private final byte DIRECT_COMMAND_NOREPLY = (byte) 0x80; private final byte PLAY_TONE= (byte) 0x03; private final byte SET_OUTPUT_STATE = (byte) 0x04; private final int MoteurA = 0; private final int MoteurC = 2; private enum Commande {Avancer, Reculer, GaucheAvant, GaucheArriere, DroiteAvant, DroiteArriere, Arreter; private Commande commande = Commande.Arreter; private int vitesse = 60; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); adresse = (EditText) findviewbyid(r.id.adresse); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); protected void onstart() { super.onstart(); if (!adaptateurlocal.isenabled()) { startactivityforresult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 0); protected void onstop() { try { if (robot!= null) robot.close(); adaptateurlocal.disable(); super.onstop(); catch (IOException ex) { public void recherche(view vue) { if (connecte) return; try { connecte = true; NXT = adaptateurlocal.getremotedevice(adresse.gettext().tostring()); robot = NXT.createRfcommSocketToServiceRecord(uuidNXT); robot.connect(); entree = robot.getinputstream(); sortie = robot.getoutputstream(); Toast.makeText(Bluetooth.this, "Trouvé : "+NXT.getAddress(), Toast.LENGTH_LONG).show(); beep(880, 1000); catch (IOException ex) { Toast.makeText(Bluetooth.this, "Problème de connexion", Toast.LENGTH_LONG).show();

77 public void avancer(view vue) { commande = Commande.Avancer; commandemoteur(moteura, vitesse); commandemoteur(moteurc, vitesse); public void gauche(view vue) { if (commande==commande.avancer) commande = Commande.GaucheAvant; if (commande==commande.droiteavant) commande = Commande.GaucheAvant; if (commande==commande.reculer) commande = Commande.GaucheArriere; if (commande==commande.droitearriere) commande = Commande.GaucheArriere; switch (commande) { case GaucheAvant : commandemoteur(moteura, vitesse); commandemoteur(moteurc, vitesse/3); break; case GaucheArriere : commandemoteur(moteura, -vitesse); commandemoteur(moteurc, -vitesse/3); break; public void droite(view vue) { if (commande==commande.avancer) commande = Commande.DroiteAvant; if (commande==commande.gaucheavant) commande = Commande.DroiteAvant; if (commande==commande.reculer) commande = Commande.DroiteArriere; if (commande==commande.gauchearriere) commande = Commande.DroiteArriere; switch (commande) { case DroiteAvant : commandemoteur(moteura, vitesse/3); commandemoteur(moteurc, vitesse); break; case DroiteArriere : commandemoteur(moteura, -vitesse/3); commandemoteur(moteurc, -vitesse); break; public void reculer(view vue) { commande = Commande.Reculer; commandemoteur(moteura, -vitesse); commandemoteur(moteurc, -vitesse); public void arreter(view vue) { commande = Commande.Arreter; commandemoteur(moteura, 0); commandemoteur(moteurc, 0); public void quarante(view vue) { vitesse = 40; changervitesse(vue); public void soixante(view vue) { vitesse = 60; changervitesse(vue); public void quatrevingt(view vue) { vitesse = 80; changervitesse(vue); private void changervitesse(view vue) { Toast.makeText(Bluetooth.this, "Vitesse = "+vitesse, Toast.LENGTH_SHORT).show(); switch (commande) { case Avancer : avancer(vue); break; case Reculer : reculer(vue); break; case GaucheAvant : case GaucheArriere : gauche(vue); break; case DroiteAvant : case DroiteArriere : droite(vue); break; private void beep(int frequence, int duree) { byte[] trame = new byte[6]; trame[0] = DIRECT_COMMAND_NOREPLY; trame[1] = PLAY_TONE; trame[2] = (byte) frequence; trame[3] = (byte) (frequence >> 8); trame[4] = (byte) duree; trame[5] = (byte) (duree >> 8); envoitrame(trame); private void commandemoteur(int moteur, int vitesse) { byte[] trame = new byte[12]; trame[0] = DIRECT_COMMAND_NOREPLY; trame[1] = SET_OUTPUT_STATE; trame[2] = (byte) moteur; if (vitesse==0) { trame[3] = 0x00; trame[4] = 0x00; trame[5] = 0x00;

78 trame[6] = 0x00; trame[7] = 0x00; else trame[3] = (byte) vitesse; trame[4] = 0x03; trame[5] = 0x01; trame[6] = 0x00; trame[7] = 0x20; trame[8] = 0; trame[9] = 0; trame[10] = 0; trame[11] = 0; envoitrame(trame); private void envoitrame(byte[] octets) { try { if (sortie == null) return; int longueur = octets.length; sortie.write(longueur); sortie.write(longueur >> 8); sortie.write(octets); catch (IOException ex) { Toast.makeText(Bluetooth.this, "Trame non reçue", Toast.LENGTH_LONG).show(); Reprenons le même projet en ajoutant une autre activité qui permet de recenser les robots déjàs enregistrés sur votre mobile sous forme de liste. Autre particularité intéressante, nous ferons en sorte que les deux activités soient présentées sous forme de boîte de dialogue. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.bluetooth" android:versioncode="1" android:versionname="1.0"> <application android:label="bluetooth NXT" > <activity android:name="bluetooth" android:label="robot NXT" android:screenorientation="portrait" // Apparence d'une boîte de dialogue <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <activity android:name="listenxt" android:label=" Liste des robots" android:screenorientation="portrait" // Apparence d'une boîte de dialogue </application> <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> </manifest> AndroidManifest.xml src/layout/main.xml

79 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent" android:padding="3dp"> <Button android:layout_width="120dp" android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#0000ff" android:text="avancer" android:onclick="avancer" /> <RelativeLayout > <Button android:layout_width="120dp" android:layout_marginleft="10dp" android:layout_alignparentleft="true" android:textcolor="#0000ff" android:text="gauche" android:onclick="gauche" /> <Button android:layout_width="120dp" android:layout_marginright="10dp" android:layout_alignparentright="true" android:textcolor="#0000ff" android:text="droite" android:onclick="droite" /> </RelativeLayout> <Button android:layout_width="120dp" android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#0000ff" android:text="reculer" android:onclick="reculer" /> <Button android:layout_gravity="center" android:layout_margin="20dp" android:textcolor="#ff0000" android:text="arrêter" android:onclick="arreter" /> <LinearLayout android:padding="10dp"> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" android:text="40" android:onclick="quarante" /> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" android:text="60" android:onclick="soixante" /> <Button android:layout_width="0dp" android:layout_weight="1" android:textcolor="#ff00ff" android:text="80" android:onclick="quatrevingt" /> </LinearLayout> </LinearLayout> package fr.btsiris.bluetooth; fr.btsiris.bluetooth.bluetooth.java import android.app.activity; import android.bluetooth.*; import android.content.*; import android.os.*;

80 import android.view.*; import android.widget.*; import java.io.*; import java.util.uuid; public class Bluetooth extends Activity { private BluetoothAdapter adaptateurlocal; private BluetoothDevice NXT; private BluetoothSocket robot; private InputStream entree; private OutputStream sortie; private UUID uuidnxt = UUID.fromString(" F9B34FB"); private final byte DIRECT_COMMAND_NOREPLY = (byte) 0x80; private final byte PLAY_TONE= (byte) 0x03; private final byte SET_OUTPUT_STATE = (byte) 0x04; private final int MoteurA = 0; private final int MoteurC = 2; private enum Commande {Avancer, Reculer, GaucheAvant, GaucheArriere, DroiteAvant, DroiteArriere, Arreter; private Commande commande = Commande.Arreter; private int vitesse = 60; private final int BLUETOOTH_ACTIF = 1; private final int CHOIX_ROBOT = 2; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_indeterminate_progress); // La boîte de dialogue prend presque toute la largeur de l'écran adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); protected void onstart() { super.onstart(); if (!adaptateurlocal.isenabled()) { startactivityforresult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), BLUETOOTH_ACTIF); // Activation du Bluetooth else choixnxt(); protected void onstop() { try { if (robot!= null) robot.close(); adaptateurlocal.disable(); super.onstop(); catch (IOException ex) { private void choixnxt() { settitle(" Attente de connexion..."); startactivityforresult(new Intent(this, ListeNXT.class), CHOIX_ROBOT); // Lancement de l'activité qui permet de choisir le robot protected void onactivityresult(int requestcode, int resultcode, Intent data) { switch (requestcode) { case BLUETOOTH_ACTIF : switch (resultcode) { case RESULT_OK : choixnxt(); break; case RESULT_CANCELED : Toast.makeText(this, "Arrêt : Bluetooth non actif", Toast.LENGTH_LONG).show(); break; break; case CHOIX_ROBOT : if (resultcode == RESULT_OK) { String adresse = data.getextras().getstring(listenxt.adresse_mac); // Récupération de l'adresse MAC du robot setcontentview(r.layout.main); connexion(adresse); break; private void connexion(string adresse) { try { NXT = adaptateurlocal.getremotedevice(adresse); robot = NXT.createRfcommSocketToServiceRecord(uuidNXT); robot.connect(); entree = robot.getinputstream(); sortie = robot.getoutputstream(); Toast.makeText(Bluetooth.this, "Trouvé : "+NXT.getAddress(), Toast.LENGTH_LONG).show(); settitle(" NXT - "+adresse); // Changement du titre de l'activité principale beep(880, 1000); // Si le robot est bien connecté, ce dernier émet un La pendant une seconde

81 catch (IOException ex) { Toast.makeText(Bluetooth.this, "Problème de connexion...", Toast.LENGTH_LONG).show(); public void avancer(view vue) { commande = Commande.Avancer; commandemoteur(moteura, vitesse); commandemoteur(moteurc, vitesse); public void gauche(view vue) { if (commande==commande.avancer) commande = Commande.GaucheAvant; if (commande==commande.droiteavant) commande = Commande.GaucheAvant; if (commande==commande.reculer) commande = Commande.GaucheArriere; if (commande==commande.droitearriere) commande = Commande.GaucheArriere; switch (commande) { case GaucheAvant : commandemoteur(moteura, vitesse); commandemoteur(moteurc, vitesse/3); break; case GaucheArriere : commandemoteur(moteura, -vitesse); commandemoteur(moteurc, -vitesse/3); break; public void droite(view vue) { if (commande==commande.avancer) commande = Commande.DroiteAvant; if (commande==commande.gaucheavant) commande = Commande.DroiteAvant; if (commande==commande.reculer) commande = Commande.DroiteArriere; if (commande==commande.gauchearriere) commande = Commande.DroiteArriere; switch (commande) { case DroiteAvant : commandemoteur(moteura, vitesse/3); commandemoteur(moteurc, vitesse); break; case DroiteArriere : commandemoteur(moteura, -vitesse/3); commandemoteur(moteurc, -vitesse); break; public void reculer(view vue) { commande = Commande.Reculer; commandemoteur(moteura, -vitesse); commandemoteur(moteurc, -vitesse); public void arreter(view vue) { commande = Commande.Arreter; commandemoteur(moteura, 0); commandemoteur(moteurc, 0); public void quarante(view vue) { vitesse = 40; changervitesse(vue); public void soixante(view vue) { vitesse = 60; changervitesse(vue); public void quatrevingt(view vue) { vitesse = 80; changervitesse(vue); private void changervitesse(view vue) { Toast.makeText(Bluetooth.this, "Vitesse = "+vitesse, Toast.LENGTH_SHORT).show(); switch (commande) { case Avancer : avancer(vue); break; case Reculer : reculer(vue); break; case GaucheAvant : case GaucheArriere : gauche(vue); break; case DroiteAvant : case DroiteArriere : droite(vue); break; private void beep(int frequence, int duree) { byte[] trame = new byte[6]; trame[0] = DIRECT_COMMAND_NOREPLY; trame[1] = PLAY_TONE; trame[2] = (byte) frequence; trame[3] = (byte) (frequence >> 8); trame[4] = (byte) duree; trame[5] = (byte) (duree >> 8); envoitrame(trame); private void commandemoteur(int moteur, int vitesse) { byte[] trame = new byte[12]; trame[0] = DIRECT_COMMAND_NOREPLY; trame[1] = SET_OUTPUT_STATE; trame[2] = (byte) moteur;

82 if (vitesse==0) { trame[3] = 0x00; trame[4] = 0x00; trame[5] = 0x00; trame[6] = 0x00; trame[7] = 0x00; else trame[3] = (byte) vitesse; trame[4] = 0x03; trame[5] = 0x01; trame[6] = 0x00; trame[7] = 0x20; trame[8] = 0; trame[9] = 0; trame[10] = 0; trame[11] = 0; envoitrame(trame); private void envoitrame(byte[] octets) { try { if (sortie == null) return; int longueur = octets.length; sortie.write(longueur); sortie.write(longueur >> 8); sortie.write(octets); catch (IOException ex) { Toast.makeText(Bluetooth.this, "Trame non reçue", Toast.LENGTH_LONG).show(); <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" <ListView android:stackfrombottom="true" android:layout_weight="1" /> </LinearLayout> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:textsize="20sp" android:paddingleft="5dp" android:paddingright="5dp" android:paddingtop="10dp" android:paddingbottom="10dp" android:textcolor="#ff9f00" android:textstyle="bold" /> package fr.btsiris.bluetooth; src/layout/liste_nxt.xml src/layout/nom_nxt.xml fr.btsiris.bluetooth.listenxt.java import java.util.set; import android.app.listactivity; import android.bluetooth.*; import android.content.*; import android.os.bundle; import android.view.*; import android.widget.*; public class ListeNXT extends ListActivity { static String ADRESSE_MAC = "adressemac"; private BluetoothAdapter adaptateurlocal; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_indeterminate_progress); setcontentview(r.layout.liste_nxt);

83 ArrayAdapter<String> robots = new ArrayAdapter<String>(this, R.layout.nom_nxt); adaptateurlocal = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> appareilsdejaconnus = adaptateurlocal.getbondeddevices(); if (appareilsdejaconnus.size() > 0) { for (BluetoothDevice appareil : appareilsdejaconnus) { if (appareil.getaddress().startswith("00:16:53")) // Début d'adresse MAC des robots NXT robots.add(appareil.getname()+"-"+appareil.getaddress()); setlistadapter(robots); setresult(result_canceled); protected void onlistitemclick(listview liste, View vue, int position, long id) { String info = ((TextView) vue).gettext().tostring(); if (info.lastindexof('-')!= info.length()-18) return; String addressemac = info.substring(info.lastindexof('-')+1); Intent intent = new Intent(); Bundle data = new Bundle(); data.putstring(adresse_mac, addressemac); intent.putextras(data); setresult(result_ok, intent); finish(); Réseau, connexions et services web La plupart des applications sont communicantes. Difficile de s'y tromper, car toutes les permissions qui seront réclamées, la demande de connexion à Internet est l'une des plus fréquentes. Pour réaliser ce type d'applications, voici notre programme pour ce chapitre. 1. La disponibilité du réseau. 2. La gestion du Wi-Fi. 3. L'utilisation des sockets. 4. La création de requêtes pour accéder à des informations distantes. 5. La réalisation de client pour des services web. Disponibilité du réseau Afin de concevoir intelligemment les applications qui nécessitent une connectivité au réseau, il est indispensable de vérifier au préalable la disponibilité de cette fameuse connexion. Android diffuse des intentions qui décrivent les modifications dans la connexion réseau et offre des API qui permettent de contrôler les réglages réseau et les connexions. Le réseau sous Android est principalement géré par la classe ConnectivityManager, un service qui vous permet de monitorer l'état de la connexion, de fixer votre connexion préféré et de gérer le basculement vers un autre type de connexion. Pour accéder au gestionnaire de connexion, utiliser la méthode getsystemservice() en lui passant l'argument Context.CONNECTIVITY_SERVICE comme nom du service souhaité. ConnectivityManager gestionconnexions = (ConnectivityManager) getsystemservice(context.connectivity_service); Pour que cet extrait de code fonctionne, n'oubliez pas d'ajouter les permissions suivantes dans la section du manifeste du fichier de configuration de l'application, pour la lecture de l'état du réseau et pour d'éventuelles modifications : <uses-permission android:name="android.permission.access_network_state" /> <uses-permission android:name="android.permission.change_network_state" /> Monitorer le réseau Pour monitorer les détails du réseau, le gestionnaire de connexion fournit une vue de haut niveau sur les connexions réseau disponibles. L'utilisation des méthodes getactivenetworkinfo() ou getnetworkinfo(typeréseau) renvoient des objets de type NetworkInfo qui contiennent les détails sur le réseau actif courant ou sur un réseau inactif du type spécifié. 1. ConnectivityManager gestionconnexions = (ConnectivityManager) getsystemservice(context.connectivity_service); NetworkInfo inforéseau = gestionconnexions.getactivenetworkinfo(); NetworkInfo.State étatréseau = inforéseau.getstate(); if (étatréseau.compareto(state.connected) == 0)... ; An passant la constante CONNECTIVITY_SERVICE à la méthode getsystemservice(), nous obtenons notre gestionnaire réseau. A l'aide de ce dernier, nous récupérons une instance de la classe NetworkInfo. La méthode getstate() appliqué à l'objet ainsi récupéré nous permet de tester l'état de la connexion (états possibles : CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, SUSPENDED et UNKNOWN).

84 2. ConnectivityManager gestionconnexions = (ConnectivityManager) getsystemservice(context.connectivity_service); // Récupère l'information du réseau actif NetworkInfo réseauactif = gestionconnexions.getactivenetworkinfo(); switch (réseauactif.gettype()) { case ConnectivityManager.TYPE_MOBILE : break; case ConnectivityManager.TYPE_WIFI : break; // Récupère l'information du réseau mobile NetworkInfo réseaumobile = gestionconnexions.getnetworkinfo(connectivitymanager.type_mobile); NetworkInfo.State étatréseaumobile = inforéseau.getstate(); NetworkInfo.DetailledState étatdétailléréseaumobile = inforéseau.getdetailedstate(); Déterminer et configurer les préférences réseau et contrôler le matériel radio Le gestionnaire de connexion peut également être utilisé pour contrôler le matériel réseau et configurer les préférences de basculement. Android tentera de se connecter au réseau préféré à chaque fois qu'une application demandera une connexion Internet. Vous pouvez déterminer et fixer le réseau courant en utilisant respectivement les méthodes getnetworkpreference() et setnetworkpreference(). Si la connexion préférée n'est pas disponible ou que la connectivité à ce réseau soit perdue, Android tentera automatiquement de se connecter au réseau secondaire. ConnectivityManager connectivité = (SensorManager) getsystemservice(context.connectivity_service) ; int préférence = connectivité.getnetworkpreference() ; connectivité.setnetworkpreference(networkpreference.prefer_wifi) ; Monitorer la connectivité réseau L'une des fonctions les plus utiles du ConnectivityManager est de notifier les applications des changements dans la connectivité réseau. Afin de monitorer celle-ci, créez votre propre implémentation de BroadcastReceiver écoutant les intentions venant de ConnectivityManager.CONNECTIVITY_ACTION. Ces intentions comptent plusieurs extras qui fournissent des détails additionnels sur les changements dans l'état de la connectivité. Vous pouvez accéder à chacun d'entre eux en utilisant l'une des constantes statiques de la classe ConnectivityManager : 1. EXTRA_IS_FAILOVER : Booléen renvoyant true si la connexion courante est le résultat d'un basculement depuis le réseau préféré. 2. EXTRA_NO_CONNECTIVITY : Booléen renvoyant true si l'appareil n'est connecté à aucun réseau. 3. EXTRA_REASON : Si la diffusion associé représente un échec réseau, cette chaîne contiendra une description de la raison de l'échec. 4. EXTRA_NETWORK_INFO : Renvoie un objet NetworkInfo contenant des détails plus fins sur le réseau associé à l'événement de connectivité courante. 5. EXTRA_OTHER_NETWORK_INFO : Après une déconnexion réseau, cette valeur renverra un objet NetworkInfo contenant les détails des basculements possibles. 6. EXTRA_EXTRA_INFO : Contient les détails de connexion additionnnels spécifiques au réseau. Gérer le réseau Wi-FI Le Wi-Fi est une fonctionnalité importante car elle offre aux appareils qui ne sont pas dotés de fonctions de téléphonie l'accès à l'internet. De plus le réseau Wi-Fi offre actuellement un meilleur débit que le réseau téléphonique classique. Pour manipuler les données relatives aux réseau Wi-Fi, vous devez faire appel à l'objet WifiManager. Cet objet sert d'interface entre le service système qui gère le Wi-Fi et votre application. Le WifiManager représente le service de connectivité Wi-Fi d'android. Il peut être utilisé pour configurer les connexions Wi-Fi, gérer la connexion courante, scanner des points d'accès et monitorer les changements dans la connectivité. Comme pour le ConnectivityManager, vous accéder au WifiManager en utilisant la méthode getsystemservice() et en lui passant en paramètre la constante Context.WIFI_SERVICE. WifiManager wifi = (WifiManager) getsystemservice(context.wifi_service); Pour utiliser le WifiManager, votre application doit avoir les permissions adéquates dans son manifeste, soit pour une simple consultation, soit pour que votre application soit capable d'effectuer un changement d'état : <uses-permission android:name="android.permission.access_wifi_state" /> <uses-permission android:name="android.permission.change_wifi_state" /> Activer et désactiver le Wi-Fi Le premier essentiel à aborder avec le Wi-Fi, c'est bien évidemment de pouvoir obtenir son état (activé ou non) et au besoin de le modifier. Vous pouvez utiliser le WifiManager pour activer ou désactiver votre Wi-Fi en utilisant la méthode setwifienabled() ou demander l'état courant en utilisant les méthodes getwifistate() ou iswifienabled().

85 WifiManager wifi = (WifiManager) getsystemservice(context.wifi_service); if (wifi.iswifienabled()) if (wifi.getwifistate()!= WifiManager.WIFI_STATE_ENABLING) wifi.setwifienabled(true); Monitorer la connectivité Wi-Fi Le WifiManager diffuse des intentions à chaque fois sur le status de la connectivité du réseau Wi-Fi change, utilisant une action représentée par l'une des constantes suivantes de la classe WifiManager : WIFI_STATE_CHANGED_ACTION Indique que le status du matériel a changé pour l'une des valeurs "en cours d'activation", "activé", "en cours de désactivation", "désactivation" ou "inconnu". Elle contient deux extras, EXTRA_WIFI_STATE et EXTRA_PREVIOUS_STATE, qui fournissent respectivement le nouvel état et l'ancien. SUPPLICANT_CONNECTION_CHANGED_ACTION Cette intention est diffusée à chaque fois que l'état de la connexion avec le supplicant (point d'accès) actif change. Il est déclenché lorsqu'une nouvelle connexion est établie ou qu'une connexion existante est perdue, utilisant l'extra EXTRA_NEW_STATE, qui renvoie true dans le cas précédent. NETWORK_STATE_CHANGED_ACTION Déclenché à chaque fois que la connectivité Wi-Fi change. Cette intention contient deux extras. Le premier, EXTRA_NETWORK_INFO, contient un objet NetworkInfo qui détaille l'état du réseau courant et le second, EXTRA_BSSID, inclut le BSSID du point d'accès auquel vous êtes connecté. RSSI_CHANGED_ACTION Vous pouvez monitorer la force du signal du réseau auquel vous êtes connecté en écoutant cette intention. Ce Broadcast Intent inclut un extra entier, EXTRA_NEW_RSSI, qui contient la force du signal en cours. Pour l'utiliser, vous devez utiliser la méthode statique calculatesignallevel() sur le WifiManager pour le convertir en une valeur entière sur une échelle de votre choix. Monitorer les détails de la connexion active Une fois qu'une connexion réseau a été établie, utilisez la méthode getconnectioninfo() sur le WifiManager pour trouver les informations su le statut de la connexion active. L'objet WifiInfo renvoyé contient le SSID, le BSSID, l'adresse MAC et l'adresse IP du point d'accès courant ainsi que la vitesse de liaison et la force du signal. WifiManager wifi = (WifiManager) getsystemservice(context.wifi_service); WifiInfo info = wifi.getconnectioninfo(); if (info.getbssid()!= null) { int force = WifiManager.calculateSignalLevel(info.getRssi(), 5); int vitesse = info.getlinkspeed(); String unités = WifiInfo.LINK_SPEED_UNITS; String ssid = info.getssid(); String information = String.format("Connecté à %s%s. Force %s/5", ssid, vitesse, unités, force); Les sockets L'utilisation des sockets, ou connecteurs réseau, est plus confidentielle sur une application mobile puisque de plus bas niveau, mais elle fait partie du large spectre des possibilités. Rappel sur les sockets : Une socket est un flux que nous pouvons lire ou que nous pouvons écrire des données brutes. Le concept de socket permet à plusieurs processus de communiquer sur la même machine (en local) ou via un réseau. Pour cela, une socket est identifiable par une adresse IP spécifique et un numéro de port. En mode connecté Grâce au protocole TCP, pour établir une connexion durable. En mode non connecté Il existe deux grands types d'utilisations et donc des communications Grâce au protocole UDP. Un mode non connecté implique qu'il n'y a pas de confirmation de réception des informations et pas de contrôle d'erreur. Ce mode est beaucoup plus performant pour des applications vidéo, pas exemple, mais moins fiable. Le client Android La mise en oeuvre d'une communication par socket avec est extrêmement simple avec Android, puisqu'il suffit d'appliquer exactement la même démarche qu'avec une application Java SE classique qui utilise la classe Socket et toutes les classes prévues par les flux de haut niveau. Par contre, comme les soumissions réseau sont relativement longue, prévoyez de mettre en oeuvre un environnement multi-tâches. Côté permission, puisque nous avons besoin d'une connexion, il suffit d'ajouter android.permission.internet à la section manifest du fichier de configuration de l'application comme suit : <uses-permission android:name="android.permission.internet" /> Je vous propose de prendre un exemple très simple qui interroge le service de l'heure GMT qui se trouve sur l'un des serveurs aux Etats-Unis dont le numéro de service est le 13 standard. Au préalable, je vous invite à le tester tout simplement à l'aide d'un client telnet. 1. Lancez le logiciel client Telnet.

86 2. Etablissez ensuite la connexion au serveur time.nist.org (Situé aux Etats-Unis ) à l'aide du client Telnet. Pour cela nous utilisons la commande open. Rappelez-vous que par défaut ce service est réglé sur le port 13. open time.nist.gov 13 ou open Que se passe-t-il? Nous venons de nous connecter au service date que la plupart des serveurs UNIX implémentent en permanence. Le serveur auquel vous vous êtes connecté se trouve au NIST à Boulder dans le Colorado, et fournit l'heure d'une horloge atomique au césium. Naturellement, le temps affiché n'est pas parfaitement précis à cause des délais de propagation des informations sur le réseau. Par convention, le service date est toujours rattaché au port 13. Le programme du serveur fonctionne en permanence sur la machine distante, attendant un paquet du réseau qui essaierait de communiquer avec le port 13. Lorsque le système d'exploitation de l'ordinateur distant reçoit le paquet contenant une requête de connexion sur le port 13, le processus d'écoute du serveur est activé et la connexion est établie. Cette connexion demeure jusqu'à ce qu'elle soit arrêtée par l'une des deux parties. Lorsque vous avez ouvert une session Telnet sur le port 13 de l'ordinateur distant, une partie indépendante du logiciel réseau à converti la chaîne de texte time.nist.gov en une adresse IP associée, c'est à dire Puis le logiciel a envoyé une requête de connexion à cet ordinateur, en spécifiant le port 13. Une fois que cette connexion a été établie, le programme de l'ordinateur distant a renvoyé un ensemble de données, puis il a terminé la connexion. Bien sûr, dans le cas plus général, les clients et les serveurs entament des dialogues beaucoup plus poussés avant que la connexion ne soit interrompue. Après toutes ces considérations techniques, je vous porpose d'implémenter un client Android qui nous donne l'heure GMT exacte (presque) avec un formatage de la date qui nous donne le jour de la semaine et l'identification du mois en toute lettre. AndroidManifest.xml

87 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.date" android:versioncode="1" android:versionname="1.0"> <application android:label="horloge Réseau" > <activity android:name="datereseau" android:label="horloge réseau"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.internet" /> </manifest> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <TextView android:gravity="center" android:textsize="20sp" android:textcolor="#009f00" android:textstyle="bold" android:text="date" /> <TextView android:gravity="center" android:textsize="20sp" android:textcolor="#009f00" android:textstyle="bold" android:paddingbottom="20sp" android:text="heure" /> <Button android:layout_width="200dp" android:layout_gravity="center" android:text="nouvelle requête" android:onclick="requete"/> </LinearLayout> package fr.btsiris.date; src/layout/main.xml fr.btsiris.date.datereseau.java import android.app.activity; import android.os.*; import android.view.view; import android.widget.*; import java.io.*; import java.net.*; import java.text.dateformat; import java.util.*; public class DateReseau extends Activity { private TextView heure, date; private boolean enexecution = false; private Handler client = new Handler() { public void handlemessage(message msg) { Bundle donnees = msg.getdata(); if (donnees.getboolean("valide")) { Toast.makeText(DateReseau.this, "Soumission acceptée", Toast.LENGTH_LONG).show(); heure.settext(donnees.getstring("heure")); date.settext(donnees.getstring("date")); else Toast.makeText(DateReseau.this, "Impossible d'atteindre le service", Toast.LENGTH_LONG).show(); ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); heure = (TextView) findviewbyid(r.id.heure); date = (TextView) findviewbyid(r.id.date);

88 protected void onstart() { super.onstart(); requete(null); public void requete(view vue) { if (!enexecution) new Thread(new Tache()).start(); class Tache implements Runnable { public void run() { enexecution = true; try { Socket servicedate = new Socket("time.nist.gov", 13); Scanner reponse = new Scanner(serviceDate.getInputStream()); if (reponse.hasnextline()) { reponse.next(); Scanner chaine = new Scanner(reponse.next()); chaine.usedelimiter("-"); int annee = 100+chaine.nextInt(); int mois = chaine.nextint()-1; int jour = chaine.nextint(); Date datecomplete = new Date(annee, mois, jour); Bundle donnees = new Bundle(); donnees.putboolean("valide", true); donnees.putstring("heure", reponse.next()+ " GMT"); donnees.putstring("date", DateFormat.getDateInstance(DateFormat.FULL).format(dateComplete)); Message message = client.obtainmessage(); message.setdata(donnees); client.sendmessage(message); servicedate.close(); catch (IOException ex) { Bundle donnees = new Bundle(); donnees.putboolean("valide", false); Message message = client.obtainmessage(); message.setdata(donnees); client.sendmessage(message); enexecution = false; Interroger un serveur web grâce au protocole HTTP Au travers de ce dernier sujet, nous allons communiquer en travers de requête HTTP vers un service web REST, à partir d'un client Android. Le protocole HTTP ainsi que le service web REST Java est largement évoqué dans l'étude suivante.. A une requête HTTP effectuée par un client, est associée une réponse d'un serveur. La requête client est constituée : Rappel rapide sur le protocole HTTP 1. D'une ligne de requête (qui peut être de plusieurs types : GET, POST, HEAD, etc. 2. D'en-têtes (lignes facultatives qui permettent de communiquer des informations supplémentaires comme la version du client, très utile afin de pouvoir adapter les contenus aux mobiles). 3. Du coprs de la requête qui peut être de différentes natures (types MIME : text/plain, application/xml, image/jpeg, etc.) Utilisation de la classe HttpClient Le kit de développement Android contient le client HttpClient d'apache - dans une version adaptée à Android - qui propose un certain nombre de fonctionnalités pour utiliser le protocole HTTP dans son ensemble. Quel que soit le type de requête, GET, POST, PUT, DELETE, HEAD et OPTION, l'utilisation de ce client suit le schéma suivant : 1. Création d'une instance de la classe DefaultHttpClient qui implémente l'interface HttpClient. 2. Création d'une instance d'un objet représentant la requête qui spécialisera notre client. 3. Réglage des propriétés de cette requête. 4. Exécution de la requête grâce à l'instance de type HttpClient obtenue auparavant. 5. Analyse et traitement de la réponse. Je vous propose de prendre tout de suite un exemple très simple qui va nous permettre de communiquer avec le service web REST de gestion du personnel élaboré dans l'étude sur les web services. Il s'agit ici pour l'instant de n'utiliser que la méthode GET en donnant juste la valeur de l'identifiant de la personne concernée.

89 Il existe plusieurs méthodes GET implentée dans le web service REST. Nous utilisons la dernière méthode qui retourne les données de l'ensemble de la personne dans les en-têtes de la réponse HTTP. Pour en savoir plus sur le projet de ce service web REST, retourneé dans l'étude suivante.. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.btsiris.rest" android:versioncode="1" android:versionname="1.0"> <application android:label="personnelrest" > <activity android:name="clientpersonnel" android:label="client Personnel"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.internet" /> </manifest> AndroidManifest.xml Attention, pensez bien à donner les droits d'accès à la communication réseau.. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <EditText android:hint="identifiant" android:inputtype="number"/> <Button android:text="requête" android:onclick="soumettre" /> </LinearLayout> src/layout/main.xml package fr.btsiris.rest; fr.btsiris.rest.clientpersonnel.java import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.*; import java.io.ioexception; import org.apache.http.*; import org.apache.http.client.httpclient; import org.apache.http.client.methods.httpget; import org.apache.http.impl.client.defaulthttpclient; public class ClientPersonnel extends Activity { private EditText identifiant; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); identifiant = (EditText) findviewbyid(r.id.identifiant); public void soumettre(view vue) throws IOException { String nom="", prenom="", telephone=""; HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http:// :8080/Personnel/rest/"+identifiant.getText().toString()); HttpResponse reponse = client.execute(requete); if (reponse.getstatusline().getstatuscode() == 200) {

90 for (Header entete : reponse.getallheaders()) { if (entete.getname().equals("nom")) nom = entete.getvalue(); if (entete.getname().equals("prenom")) prenom = entete.getvalue(); if (entete.getname().equals("telephone")) telephone = entete.getvalue(); Toast.makeText(this, nom+" "+prenom+"\n"+telephone, Toast.LENGTH_LONG).show(); else Toast.makeText(this, "Identifiant inconnu", Toast.LENGTH_SHORT).show(); Au delà de l'uitlisation de la classe DefaultHttpClient et de son interface HttpClient, vous devez prendre une classe correspond à la requête HTTP souhaitée, ici la requête GET. La classe correspondante se nomme tout simplement HttpGet. Pour récupérer la réponse à la requête choisissez cette fois-ci la classe spécialisée HttpResponse. Je vous propose maintenant, toujours sur ce petit projet, d'utiliser la deuxième méthode GET, qui renvoie cette fois-ci un contenu au format JSON. Ce format étant textuel, je me contente ici d'afficher tout simplement le texte correspondant sans interprétation quelconque : package fr.btsiris.rest; fr.btsiris.rest.clientpersonnel.java import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.*; import java.io.*; import java.util.scanner; import org.apache.http.*; import org.apache.http.client.httpclient; import org.apache.http.client.methods.httpget; import org.apache.http.impl.client.defaulthttpclient; public class ClientPersonnel extends Activity { private EditText identifiant; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); identifiant = (EditText) findviewbyid(r.id.identifiant); public void soumettre(view vue) throws IOException { HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http:// :8080/Personnel/rest/json/"+identifiant.getText().toString()); HttpResponse reponse = client.execute(requete); if (reponse.getstatusline().getstatuscode() == 200) { Scanner lecture = new Scanner(reponse.getEntity().getContent()); StringBuilder contenu = new StringBuilder(); while (lecture.hasnextline()) contenu.append(lecture.nextline()+'\n'); Toast.makeText(this, contenu.tostring(), Toast.LENGTH_LONG).show(); else Toast.makeText(this, "Identifiant inconnu", Toast.LENGTH_SHORT).show(); Le client Android JSON associé JSON (JavaScript Object Notation) est un format de données qui permet de représenter de l'information structurée sous forme de texte. Sa représentation est plus légère que le format XML. Je vous propose de reprendre l'exemple précédent, et nous allons récupérer les informations atomiques constituant le personnel choisi : package fr.btsiris.rest; fr.btsiris.rest.clientpersonnel.java import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.*; import java.io.*; import java.text.dateformat; import java.util.*; import org.apache.http.*; import org.apache.http.client.httpclient; import org.apache.http.client.methods.httpget; import org.apache.http.impl.client.defaulthttpclient; import org.json.*; public class ClientPersonnel extends Activity { private EditText identifiant;

91 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); identifiant = (EditText) findviewbyid(r.id.identifiant); public void soumettre(view vue) throws IOException, JSONException { HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http:// :8080/Personnel/rest/json/"+identifiant.getText().toString()); HttpResponse reponse = client.execute(requete); if (reponse.getstatusline().getstatuscode() == 200) { Scanner lecture = new Scanner(reponse.getEntity().getContent()); StringBuilder contenu = new StringBuilder(); while (lecture.hasnextline()) contenu.append(lecture.nextline()+'\n'); JSONObject json = new JSONObject(contenu.toString()); String nom = json.getstring("nom"); String prénom = json.getstring("prénom"); Date naissance = new Date(json.getLong("naissance")); String téléphone = json.getstring("téléphone"); String message = nom+" "+prénom+"\n"+téléphone+"\n"+dateformat.getdateinstance(dateformat.full).format(naissance); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); else Toast.makeText(this, "Identifiant inconnu", Toast.LENGTH_SHORT).show(); Si vous devez récupérer un seul objet qui est envoyé en format org.json.json, il suffit de prendre la classe JSONObject et de créer une instance de cette classe avec la chaîne récupérée. Ensuite, pour mapper l'ensemble des attributs, il suffit d'utiliser les méthodes adaptées, comme getstring(), getdouble(), getint(), getlong(), etc. suivant le type de l'attribut. Une autre classe intéressante et très souvent utile lorsque nous manipulons des fichiers JSON qui possèdent une collection d'objets, traduit sous forme de tableau. Il s'agit de la classe JSONArray. Dans le projet suivant et de façon la plus réduite possible pour bien maîtriser ce concept, nous allons récupérer la liste du personnel enregistré. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <Button android:text="liste du personnel" android:onclick="soumettre" /> </LinearLayout> package fr.btsiris.rest; src/layout/main.xml fr.btsiris.rest.clientpersonnel.java import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.*; import java.io.*; import java.util.*; import org.apache.http.*; import org.apache.http.client.httpclient; import org.apache.http.client.methods.httpget; import org.apache.http.impl.client.defaulthttpclient; import org.json.*; public class ClientPersonnel extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void soumettre(view vue) throws IOException, JSONException { HttpClient client = new DefaultHttpClient(); HttpGet requete = new HttpGet("http:// :8080/Personnel/rest/tous/"); HttpResponse reponse = client.execute(requete); if (reponse.getstatusline().getstatuscode() == 200) { Scanner lecture = new Scanner(reponse.getEntity().getContent()); StringBuilder contenu = new StringBuilder(); while (lecture.hasnextline()) contenu.append(lecture.nextline()+'\n'); JSONArray tableaujson = new JSONArray(contenu.toString()); StringBuilder message = new StringBuilder(); for (int i=0; i<tableaujson.length(); i++) {

Cartes, géocodage et services de géolocalisation

Cartes, géocodage et services de géolocalisation 8 Cartes, géocodage et services de géolocalisation Au sommaire de ce chapitre : Géocodage avant et inverse Créer des cartes interactives avec les Map Views et les Map Activities Créer et ajouter des Overlays

Plus en détail

Chapitre 4. Le modèle de composants : les services

Chapitre 4. Le modèle de composants : les services Chapitre 4 Le modèle de composants : les services 1 Les services "A Service is an application component that can perform long-running operations in the background and does not provide a user interface."

Plus en détail

Outils, langage et approche Android Une introduction. Nicolas Stouls nicolas.stouls@insa lyon.fr

Outils, langage et approche Android Une introduction. Nicolas Stouls nicolas.stouls@insa lyon.fr Outils, langage et approche Android Une introduction Nicolas Stouls nicolas.stouls@insa lyon.fr Webographie La bible contenant «tout» : http://developer.android.com/index.html Les supports cette intervention

Plus en détail

Android Guide de développement d'applications Java pour Smartphones et Tablettes (2ième édition)

Android Guide de développement d'applications Java pour Smartphones et Tablettes (2ième édition) Avant-propos 1. Introduction 15 2. À qui s'adresse cet ouvrage? 15 3. Connaissances nécessaires pour aborder cet ouvrage 16 4. Objectifs à atteindre 16 5. Téléchargements 17 6. Informations complémentaires

Plus en détail

TP SIN Programmation sur androïde Support : eclipse

TP SIN Programmation sur androïde Support : eclipse TP SIN Programmation sur androïde Support : eclipse Support : Smartphone sur androïde Pré requis (l élève doit savoir): Savoir utiliser un ordinateur Savoir utiliser un Smartphone Programme Objectif terminale

Plus en détail

Android: Google map et Géolocalisation

Android: Google map et Géolocalisation Matiaz OUINE Juin 2012 Benoit RAYMOND Ensimag 2A Projet de spécialité David DUMENIL Florian GUFFON Projet de spécialité: Développement d une application Android utilisant la géolocalisation Tutoriel :

Plus en détail

Chapitre 2 Cycle de vie d une application

Chapitre 2 Cycle de vie d une application Chapitre 2 : Cycle de vie d une application 20 Chapitre 2 Cycle de vie d une application Chapitre 2 : Cycle de vie d une application 21 Une application Android est composée d un ensemble de 4 éléments

Plus en détail

Chapitre 2 Cycle de vie d une application

Chapitre 2 Cycle de vie d une application Chapitre 2 : Cycle de vie d une application 20 Chapitre 2 Cycle de vie d une application Chapitre 2 : Cycle de vie d une application 21 Une application Android est composée d un ensemble de 4 éléments

Plus en détail

TP Android Les Intents avec passage de données

TP Android Les Intents avec passage de données TP Android Les Intents avec passage de données Romain Raveaux Polytech Tours Dans le TP précédent, nous avons vu comment démarrer une nouvelle fenêtre par le biais d une action sur un bouton posé sur une

Plus en détail

Android 4 Les fondamentaux du développement d'applications Java

Android 4 Les fondamentaux du développement d'applications Java La plateforme Android 1. Présentation 13 2. Historique 14 3. Google Play 15 3.1 Création d'un compte développeur 16 3.2 Publication d'une application 16 3.3 Suivi et mise à jour d'une application 18 Environnement

Plus en détail

Android 4 Les fondamentaux du développement d applications Java

Android 4 Les fondamentaux du développement d applications Java 96 Android 4 Les fondamentaux du développement d applications Java Tous les éléments basiques d une vue (bouton, zone de texte ) héritent de cette classe. Modifier une vue peut s effectuer de deux manières

Plus en détail

ANDROID Tutoriel Lecture d'un flux XML distant et enchainement d'activités

ANDROID Tutoriel Lecture d'un flux XML distant et enchainement d'activités L'objectif de ce tutoriel est de vous présenter une des façons d'exploiter en lecture des informations distantes disponibles au format XML. L'application présentera le résultat dans une ListView 1/ Prérequis

Plus en détail

Android 5 Les fondamentaux du développement d'applications Java

Android 5 Les fondamentaux du développement d'applications Java La plateforme Android 1. Présentation 13 2. Historique 14 3. Google Play 15 3.1 Création d'un compte développeur 16 3.2 Publication d'une application 17 3.3 Suivi et mise à jour d'une application 18 Environnement

Plus en détail

UPMC/Licence/Info/2I013 Flowdroid Android. Janvier 2015. Exemple de mise en œuvre

UPMC/Licence/Info/2I013 Flowdroid Android. Janvier 2015. Exemple de mise en œuvre UPMC/Licence/Info/2I013 Flowdroid Android Janvier 2015 Exemple de mise en œuvre Un jeu pour les enfants programmeurs Une variante du taquin Une grille 9x9 dont les cases contiennent les chiffres de 1 à

Plus en détail

Android une courte Introduction

Android une courte Introduction Android une courte Introduction jean-michel Douin, douin au cnam point fr version : 15 Octobre 2012 Notes de cours 1 Sommaire Andoid OS comme middleware Applications et évènements gérés par le middleware

Plus en détail

Fonctionnement du serveur Z39.50

Fonctionnement du serveur Z39.50 Fonctionnement du serveur Z39.50 Table des matières 1 Configuration du serveur...2 1.1 Comportement du serveur...2 1.2 Configuration de la traduction z39.50 -> base de données...2 1.3 Configuration du

Plus en détail

Application Android par défaut

Application Android par défaut Projet Android À la création d un projet Android (IDE netbeans) tout un ensemble de répertoires et de fichiers sont engendrés. Source Packages : là où seront les sources de votre application. Generated

Plus en détail

Interface De Service AIDL. Android Interface Definition Language

Interface De Service AIDL. Android Interface Definition Language Client Interface De Service Serviteur AIDL Android Interface Definition Language Client Serviteur Service Process Process IPC IPC Inter Process Communication... mais sur une même machine. pas entre plusieurs

Plus en détail

La persistance des données avec SQLite. Jean-marc Farinone. JMF (Tous droits réservés) 1

La persistance des données avec SQLite. Jean-marc Farinone. JMF (Tous droits réservés) 1 La persistance des données avec SQLite Jean-marc Farinone JMF (Tous droits réservés) 1 Remarques sur SQLite La base de données FILENAME est stockée dans le smartphone sous /data/data/nom_package_appli/databases/filename

Plus en détail

IFT1155 Examen Final

IFT1155 Examen Final Trimestre Été, 2013 Mohamed Lokbani IFT1155 Examen Final Inscrivez tout de suite : votre nom et le code permanent. Nom : Prénom(s) : Signature : Code perm : Date : mardi 16 juillet 2013 Durée : 3 heures

Plus en détail

Android INTRODUCTION

Android INTRODUCTION Android INTRODUCTION Présentation Système d exploitation open source Développé en 2007 par une startup rachetée par Google. Caractéristique du Système d exploitation Android: Multi utilisateur, chaque

Plus en détail

www.elektor.fr/android SOMMAIRE

www.elektor.fr/android SOMMAIRE www.elektor.fr/android Android Apprendre à programmer des applis Environnement de développement Eclipse Programmation orientée objet en JAVA Auteur : Stephan Schwark Éditeur : Elektor ISBN : 978-2-86661-187-3

Plus en détail

Les Services. http://developer.android.com/guide/topics/fundamentals/services.html http://developer.android.com/reference/android/app/service.

Les Services. http://developer.android.com/guide/topics/fundamentals/services.html http://developer.android.com/reference/android/app/service. http://developer.android.com/guide/topics/fundamentals/services.html http://developer.android.com/reference/android/app/service.html Service = Composant applicatif qui : Fonctionne en arrière plan Peut

Plus en détail

Android les Services, Receiver et processus

Android les Services, Receiver et processus Android les Services, Receiver et processus jean-michel Douin, douin au cnam point fr version : 10 Octobre 2012 Notes de cours 1 Services Cycle de vie création, démarrage et arrêt Sommaire Service local

Plus en détail

Android Intents. this, ActivityTwo.class. Un Intent implicite spécifie l action à exécuter et une URI optionnelle qui sera utilisée par cette action.

Android Intents. this, ActivityTwo.class. Un Intent implicite spécifie l action à exécuter et une URI optionnelle qui sera utilisée par cette action. Android Intents I. Intents 1. Intent explicite Un Intent explicite défini explicitement le composant qui doit être appelé par le système Android, en utilisant la classe Java comme identifiant. Exemple

Plus en détail

Cas EDF : Développement Android - Concepts avancés Partie 2

Cas EDF : Développement Android - Concepts avancés Partie 2 Cas EDF : Développement Android - Concepts avancés Partie 2 Cette publication comporte cinq parties dont l ordre est dicté par la logique du développement. Les parties 2 et 3 sont facultatives. Partie

Plus en détail

TP3 : Localiser ses amis

TP3 : Localiser ses amis TP3 : Localiser ses amis Objectifs : utiliser le capteur GPS, utiliser le fournisseur de contenu «Contacts», enregistrer son nom de paquetage sur Google pour obtenir une clé d usage Google MAP, utiliser

Plus en détail

Les fondamentaux du développement d applications Java

Les fondamentaux du développement d applications Java Android 5 Les fondamentaux du développement d applications Java Nazim BENBOURAHLA Table des matières 1 Les éléments à télécharger sont disponibles à l'adresse suivante : http://www.editions-eni.fr Saisissez

Plus en détail

TP Android Google Maps API V2

TP Android Google Maps API V2 TP Android Google Maps API V2 Objectif La création d'une application Android qui utilise les cartes Google Maps API v2 Android. Vue d'ensemble La création d'une application Android qui utilise les cartes

Plus en détail

Programmation de composant mobiles aka Android

Programmation de composant mobiles aka Android Programmation de composant mobiles aka Android Wies law Zielonka 10 décembre 2015 Toolbar Toolbar remplace Actionbar (Android 5.0 Lollipop) Dans AndroidManifest.xml utiliser un thème sans ActionBar : 1

Plus en détail

Programmation Android M1 informatique

Programmation Android M1 informatique Programmation Android M1 informatique Étienne Payet Département de mathématiques et d informatique Ces transparents sont mis à disposition selon les termes de la Licence Creative Commons Paternité - Pas

Plus en détail

TP2 : Initiation à la Programmation avec Android

TP2 : Initiation à la Programmation avec Android TP2 : Initiation à la Programmation avec Android 1 TP2 : Initiation à la Programmation avec Android Programmation Mobile Objectifs du TP Ce TP est une initiation à Android. Nous allons réaliser les premiers

Plus en détail

Android Gestion des données. Rémi Forax

Android Gestion des données. Rémi Forax Android Gestion des données Rémi Forax Types de stockage Android fourni plusieurs types de stockage Données actives d'une activité (Bundle) Fichier ressources read-only (répertoire res) Préférence partageable

Plus en détail

Création d'interfaces simples

Création d'interfaces simples 77 Chapitre 5 Création d'interfaces simples 1. Les vues Création d'interfaces simples La création d'une interface sous Android peut s'effectuer de deux manières : La création statique, qui s'effectue en

Plus en détail

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère L'héritage et le polymorphisme en Java Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère En java, toutes les classes sont dérivée de la

Plus en détail

Créer des interfaces utilisateur

Créer des interfaces utilisateur 4 Créer des interfaces utilisateur Au sommaire de ce chapitre : Utiliser les Views et les layouts Optimiser les layouts Ressources Drawable XML Créer des interfaces utilisateur indépendantes de la résolution

Plus en détail

1. Base de données SQLite

1. Base de données SQLite Dans ce TP, nous allons voir comment créer et utiliser une base de données SQL locale pour stocker les informations. La semaine prochaine, ça sera avec un WebService. On repart de l application AvosAvis

Plus en détail

TP au menu «UI ANDROID»

TP au menu «UI ANDROID» TP au menu «UI ANDROID» Pré-requis & Installation ( du couvert) soit installer en natif sur vos postes (!!! ATTENTION!!! FromScratch 1,1 Go à télécharger ) JDK http://www.oracle.com/technetwork/java/javase/downloads/index.html

Plus en détail

TP 03. Cycle de vie d une activité. 1. Comme au TP 02, mais nommez le différemment (par exemple ActivityLifeCycle)

TP 03. Cycle de vie d une activité. 1. Comme au TP 02, mais nommez le différemment (par exemple ActivityLifeCycle) TP 03 Cycle de vie d une activité 1 ) Créer un nouveau projet Android 1. Comme au TP 02, mais nommez le différemment (par exemple ActivityLifeCycle) 2 ) Surchage des fonctions de callback 1. Ouvrez le

Plus en détail

Le meilleur de l'open source dans votre cyber cafe

Le meilleur de l'open source dans votre cyber cafe Le meilleur de l'open source dans votre cyber cafe Sommaire PRESENTATION...1 Fonctionnalités...2 Les comptes...3 Le système d'extensions...4 Les apparences...5 UTILISATION...6 Maelys Admin...6 Le panneau

Plus en détail

Android une Introduction

Android une Introduction Android une Introduction jean-michel Douin, douin au cnam point fr version : 26 Septembre 2012 Notes de cours 1 Sommaire Un peu d historique Andoid OS comme middleware Applications et évènements gérés

Plus en détail

TP1 : Traducteur «Français-Anglais»

TP1 : Traducteur «Français-Anglais» TP1 : Traducteur «Français-Anglais» Objectifs : créer deux activités, basculer entre activités, passer des paramètres entre activités, utiliser un service Internet pour faire réaliser une traduction, utiliser

Plus en détail

Programmation des applications mobiles avec Android. 1 Inspiré du cours de Olivier Le Goaer

Programmation des applications mobiles avec Android. 1 Inspiré du cours de Olivier Le Goaer Programmation des applications mobiles avec Android 1 Inspiré du cours de Olivier Le Goaer 2 OS mobile : outils de développement D après le cours de Olivier Le Goaer 3 Plateforme de développement MobileApp

Plus en détail

APPLICATIONS JAVA. 4. Géolocalisation, Géocodage, Wi-Fi, Bluetooth, Applications réseaux TCP. Android Partie IV

APPLICATIONS JAVA. 4. Géolocalisation, Géocodage, Wi-Fi, Bluetooth, Applications réseaux TCP. Android Partie IV APPLICATIONS JAVA Android Partie IV Ivan MADJAROV - 2014 Applications Java sous Android IvMad, 2011-2015 2 4. Géolocalisation, Géocodage, Wi-Fi, Bluetooth, Applications réseaux TCP L'objectif principal

Plus en détail

OpenTouch Conversation for iphone Release 2.0.x

OpenTouch Conversation for iphone Release 2.0.x OpenTouch Conversation for iphone Release 2.0.x Guide de l utilisateur OpenTouch Business Edition OpenTouch MultiMedia Services 8AL90884FRABed01 1412 1. INTRODUCTION 3 2. LANCER OPENTOUCH CONVERSATION

Plus en détail

APPLICATIONS JAVA. Interface graphique avec XML pour une activité Android. Android Partie VI

APPLICATIONS JAVA. Interface graphique avec XML pour une activité Android. Android Partie VI APPLICATIONS JAVA Android Partie VI Ivan MADJAROV - 2015 Interface graphique avec XML IvMad, 2011-2015 2 Interface graphique avec XML pour une activité Android L'objectif principal de ce cours est de découvrir

Plus en détail

Exploration de la programmation android

Exploration de la programmation android Réalisé par: BOUHJJA Lamia Exploration de la programmation android Formation Assurer par: Club FreeWays SOMMAIRE : La création d un programme I. Introduction ANDROID générale II. Exploration de sa hiérarchie

Plus en détail

TUTO 2 - ANDROID : BONJOUR QUI?

TUTO 2 - ANDROID : BONJOUR QUI? TUTO 2 - ANDROID : BONJOUR QUI? Dans ce tutoriel, on va développer une application assez simple. Ce tutoriel va permettre de découvrir des composants graphiques (Textfield, EditText et Bouton). Un aperçu

Plus en détail

Android How To : Prise en Main

Android How To : Prise en Main Android How To : Prise en Main 1 Prise en main avec Eclipse... 2 1.1 Le projet... 2 1.2 Projet propriétés... 2 1.3 Le programme... 3 2 XML Base Layout... 4 2.1 Création du layout... 4 2.2 Lier le Layout

Plus en détail

Projet M1 : Application P2P Hybride avec RMI

Projet M1 : Application P2P Hybride avec RMI Projet M1 : Application P2P Hybride avec RMI Applications Réparties 2008-2009 Université Paris VIII / Parcours SRM / M1 Sujet : Le but de ce projet est d'implémenter une application de partage de fichiers

Plus en détail

DÉCOUVERTE DU DÉVELOPPEMENT ANDROID

DÉCOUVERTE DU DÉVELOPPEMENT ANDROID DÉCOUVERTE DU DÉVELOPPEMENT ANDROID Date Nom Objet 06 Septembre 2012 M. Minelli Création 15 Novembre 2011 M. Minelli Vérification 22 Novembre 2011 M. Minelli Finalisation Etat : Terminé TABLE DES MATIÈRES

Plus en détail

Sage CRM. 7.2 Guide de Portail Client

Sage CRM. 7.2 Guide de Portail Client Sage CRM 7.2 Guide de Portail Client Copyright 2013 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur microfilm,

Plus en détail

Mobile : Application Native et Cross Platform avec Xamarin Introduction JEROME ROMAGNY

Mobile : Application Native et Cross Platform avec Xamarin Introduction JEROME ROMAGNY 2014 Mobile : Application Native et Cross Platform avec Xamarin Introduction JEROME ROMAGNY I. ANDROID... 2 1. APPLICATION NATIVE... 2 A. Installation... 2 AppInventor... 3 Emulateur... 3 B. Anatomie d

Plus en détail

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile Dans ce TP, vous apprendrez à définir le type abstrait Pile, à le programmer en Java à l aide d une interface

Plus en détail

BTS Services Informatiques aux Organisations 2014/2015 2 ème année Lycée A. Malraux. 1- Descriptif fonctionnel de l'application pour la construction

BTS Services Informatiques aux Organisations 2014/2015 2 ème année Lycée A. Malraux. 1- Descriptif fonctionnel de l'application pour la construction Application SIOQuiz 1- Descriptif fonctionnel de l'application pour la construction Objectif Construire une application Android proposant un Quiz sur des noms de films et d'acteurs à retrouver en fonction

Plus en détail

ACCESS 2003. Auteur : THIERRY TILLIER Formateur informatique Les requêtes : étape 3

ACCESS 2003. Auteur : THIERRY TILLIER Formateur informatique Les requêtes : étape 3 ACCESS 2003 Auteur : THIERRY TILLIER Formateur informatique Les requêtes : étape 3 2/33 Copyright 2005 Tous droits réservés. www.coursdinfo.fr Table des matières Chapitre 1 Les requêtes-introduction...5

Plus en détail

Tutorial pour une application simple

Tutorial pour une application simple ANDROID & ECLIPSE Tutorial pour une application simple 1. Introduction Android est un système d'exploitation pour téléphone portable de nouvelle génération développé par Google. Celui-ci met à disposition

Plus en détail

BlackBerry Java SDK. Location-Based Services Version: 7.0. Guide de développement

BlackBerry Java SDK. Location-Based Services Version: 7.0. Guide de développement BlackBerry Java SDK Location-Based Services Version: 7.0 Guide de développement SWD-1627216-1213011436-002 Table des matières 1 Présentation des services géodépendants... 4 2 Recherche d'une position...

Plus en détail

TP2 : Client d une BDD SqlServer

TP2 : Client d une BDD SqlServer TP2 : Client d une BDD SqlServer Objectifs : utiliser la barre de menu, utiliser les préférences d application (settings) ou (options), gérer la persistance des données, utiliser la bibliothèque jtds:jdbc

Plus en détail

1. Introduction... 2. 2. Création d'une macro autonome... 2. 3. Exécuter la macro pas à pas... 5. 4. Modifier une macro... 5

1. Introduction... 2. 2. Création d'une macro autonome... 2. 3. Exécuter la macro pas à pas... 5. 4. Modifier une macro... 5 1. Introduction... 2 2. Création d'une macro autonome... 2 3. Exécuter la macro pas à pas... 5 4. Modifier une macro... 5 5. Création d'une macro associée à un formulaire... 6 6. Exécuter des actions en

Plus en détail

opengeophone Documentation

opengeophone Documentation opengeophone Documentation Version 1.0.0 openmairie 28 June 2013 Table des matières 1 Manuel de l utilisateur 3 1.1 installation................................................ 3 1.2 Utilisation................................................

Plus en détail

SPECIFICATIONS TECHNIQUES POUR LE DEVELOPPEMENT DES PLUGINS TOURISM SYSTEM CLIENT. V 1.0 27 janvier 2011

SPECIFICATIONS TECHNIQUES POUR LE DEVELOPPEMENT DES PLUGINS TOURISM SYSTEM CLIENT. V 1.0 27 janvier 2011 SPECIFICATIONS TECHNIQUES POUR LE DEVELOPPEMENT DES PLUGINS TOURISM SYSTEM CLIENT V 1.0 27 janvier 2011 Ce document présente l'utilisation des plugins dans Tourism System Client. Dans le Client, un plugin

Plus en détail

Bases de données et fournisseurs de contenu

Bases de données et fournisseurs de contenu 8 Bases de données et fournisseurs de contenu Au sommaire de ce chapitre : Créer des bases de données et utiliser SQLite Utiliser les fournisseurs de contenu, les curseurs et les content values pour stocker,

Plus en détail

Programmation Android TP7 - WebServices

Programmation Android TP7 - WebServices 1. WebService Dans le TP6, les avis étaient stockés dans une base SQL. Cette semaine les n-uplets sont stockés sur une base de données externe gérée par un serveur HTTP sur lequel tournent des scripts

Plus en détail

WINDOWS SERVER 2003 ADMINISTRATION A DISTANCE

WINDOWS SERVER 2003 ADMINISTRATION A DISTANCE 1. Introduction WINDOWS SERVER 2003 ADMINISTRATION A DISTANCE En règle générale, les administrateurs ne travaillent pas en salle serveurs. Et cette dernière peut se trouver n'importe où dans le bâtiment.

Plus en détail

Edutab. gestion centralisée de tablettes Android

Edutab. gestion centralisée de tablettes Android Edutab gestion centralisée de tablettes Android Résumé Ce document présente le logiciel Edutab : utilisation en mode enseignant (applications, documents) utilisation en mode administrateur (configuration,

Plus en détail

Les règles du contrôleur de domaine

Les règles du contrôleur de domaine Les règles du contrôleur de domaine Menu démarrer et barre des taches Supprimer le dossier des utilisateurs du menu Démarrer Désactiver et supprimer les liens vers Windows Update Supprimer le groupe de

Plus en détail

Alerter les utilisateurs avec des notifications

Alerter les utilisateurs avec des notifications 37 Alerter les utilisateurs avec des notifications Les messages qui surgissent, les tiroirs et les «bulles» qui leur sont associées, les icônes qui bondissent dans la barre d état, etc. sont utilisés par

Plus en détail

Android. Android. Guide de développement d applications Java. Smartphones et Tablettes. Développement pour

Android. Android. Guide de développement d applications Java. Smartphones et Tablettes. Développement pour Vous découvrirez dans un premier temps la plate-forme, vous installerez l environnement de développement et vous créerez sans attendre votre première application. Vous étudierez ensuite comment se construit

Plus en détail

L exemple qui est mis à votre disposition a pour but de rechercher les données contenues dans un fichier services.xml fourni :

L exemple qui est mis à votre disposition a pour but de rechercher les données contenues dans un fichier services.xml fourni : TP n 9 Xml/Json 1) Exemple d application analysant un fichier XML L exemple qui est mis à votre disposition a pour but de rechercher les données contenues dans un fichier services.xml fourni : Ce fichier

Plus en détail

L'application WinForm et le composant d'accès aux données

L'application WinForm et le composant d'accès aux données L'application WinForm et le composant d'accès aux données Vous disposez d'un squelette de l'application AntoineVersion0- ainsi que de la base de données à restaurer dans SqlServer Bd_Antoine.dat-. Travail

Plus en détail

Bibliographie utilisée

Bibliographie utilisée Android View, onclick, Activity, Modèle Vue Contrôleur jean-michel Douin, douin au cnam point fr version : 26 Septembre 2012 Notes de cours 1 Bibliographie utilisée http://developer.android.com/resources/index.html

Plus en détail

Application Launcher. Mode d'emploi A31003-P3010-U109-17-7719

Application Launcher. Mode d'emploi A31003-P3010-U109-17-7719 Application Launcher Mode d'emploi A31003-P3010-U109-17-7719 Our Quality and Environmental Management Systems are implemented according to the requirements of the ISO9001 and ISO14001 standards and are

Plus en détail

Java Licence professionnelle CISI 2009-2010

Java Licence professionnelle CISI 2009-2010 Java Licence professionnelle CISI 2009-2010 Cours 10 : Type générique (c) http://manu.e3b.org/java/tutoriels/avance/generique.pdf 1 Introduction La programmation générique - nouveauté la plus significative

Plus en détail

PRODIGE V3. Manuel utilisateurs. Consultation des métadonnées

PRODIGE V3. Manuel utilisateurs. Consultation des métadonnées PRODIGE V3 Manuel utilisateurs Consultation des métadonnées Pour plus d'information sur le dispositif : à remplir par chaque site éventuellement 2 PRODIGE V3 : Consultation des métadonnées SOMMAIRE 1.

Plus en détail

Android, introduction

Android, introduction Android, introduction Sébastien Jean IUT de Valence Département Informatique v1.1, 7 novembre 2012 Android en bref Android est un système d exploitation développé depuis 2003, apparu officiellement en

Plus en détail

Alfresco Mobile pour Android

Alfresco Mobile pour Android Alfresco Mobile pour Android Guide d'utilisation de l'application Android version 1.1 Commencer avec Alfresco Mobile Ce guide offre une présentation rapide vous permettant de configurer Alfresco Mobile

Plus en détail

Manuel d utilisation email NETexcom

Manuel d utilisation email NETexcom Manuel d utilisation email NETexcom Table des matières Vos emails avec NETexcom... 3 Présentation... 3 GroupWare... 3 WebMail emails sur internet... 4 Se connecter au Webmail... 4 Menu principal... 5 La

Plus en détail

Développez une application Android Programmation en Java sous Android Studio

Développez une application Android Programmation en Java sous Android Studio Environnement de développement 1. Architecture d Android 9 1.1 Présentation d Android 9 1.2 Architecture 12 1.3 Play Store 13 2. Android Studio 14 2.1 Installation sous Windows 14 2.2 Installation sous

Plus en détail

ISN. Projet de développement d'un logiciel de prêt sous Android. Soupramayen Thomas Rivière Nathan Galiay Romain. Informatique et Science du Numérique

ISN. Projet de développement d'un logiciel de prêt sous Android. Soupramayen Thomas Rivière Nathan Galiay Romain. Informatique et Science du Numérique ISN Informatique et Science du Numérique Projet de développement d'un logiciel de prêt sous Android Soupramayen Thomas Rivière Nathan Galiay Romain 2012/2013 TS3 du Lycée Bellepierre Sommaire Introduction...page

Plus en détail

Programmation de composant mobiles aka Android

Programmation de composant mobiles aka Android Programmation de composant mobiles aka Android Wies law Zielonka October 28, 2015 ContentProvider ContentProvider gère l accès aux données pour d autres application. Les données le plus souvent une base

Plus en détail

1.Programmation en Java : notions de base, orienté objet et héritage

1.Programmation en Java : notions de base, orienté objet et héritage Travaux pratique de Méthodologie et Langage de Programmation,, TP1 1 1.Programmation en Java : notions de base, orienté objet et héritage Cette séance de TP a pour objectif d'introduire à la programmation

Plus en détail

Sauvegarde locale des données : SharedPreferences, SQLite, Files

Sauvegarde locale des données : SharedPreferences, SQLite, Files Sauvegarde locale des données : SharedPreferences,, Files Jean-Ferdinand Susini Maître de conférences au CNAM Sources : Wikipedia, developper.android.com Paris, 31/05/2015 Les SharedPreferences 2 Héritier

Plus en détail

Programmation orientée objet en langage JAVA

Programmation orientée objet en langage JAVA Programmation orientée objet en langage JAVA Connexion à une base de données avec JDBC Claude Duvallet Université du Havre UFR Sciences et Techniques 25 rue Philippe Lebon - BP 540 76058 LE HAVRE CEDEX

Plus en détail

Android - Semaine 4. Android - Semaine 4. Pierre Nerzic. février-mars 2015. 1 / 54 Pierre Nerzic

Android - Semaine 4. Android - Semaine 4. Pierre Nerzic. février-mars 2015. 1 / 54 Pierre Nerzic Android - Semaine 4 Pierre Nerzic février-mars 2015 1 / 54 Pierre Nerzic Durant les prochaines semaines, nous allons nous intéresser aux applications de gestion d'une liste d'items. Stockage d'une liste

Plus en détail

Sophos Mobile Control Guide d'utilisation pour Android. Version du produit : 3.6

Sophos Mobile Control Guide d'utilisation pour Android. Version du produit : 3.6 Sophos Mobile Control Guide d'utilisation pour Android Version du produit : 3.6 Date du document : novembre 2013 Table des matières 1 À propos de Sophos Mobile Control...3 2 À propos de ce guide...4 3

Plus en détail

Manuel d'utilisation Microsoft Apps

Manuel d'utilisation Microsoft Apps Manuel d'utilisation Microsoft Apps Édition 1 2 À propos de Microsoft Apps À propos de Microsoft Apps Avec Microsoft Apps, vous disposez des applications professionnelles Microsoft sur votre téléphone

Plus en détail

Alcatel-Lucent OpenTouch Connection

Alcatel-Lucent OpenTouch Connection Alcatel-Lucent OpenTouch Connection pour PC Guide utilisateur R2.0 8AL90632FRAAed01 Mars 2014 Sommaire 1. OpenTouch Connection pour PC... 3 2. Connexion... 3 3. Menu Application... 4 4. Acheminement des

Plus en détail

PROJET D ANALYSE : APPLICATION ANDROID

PROJET D ANALYSE : APPLICATION ANDROID PROJET D ANALYSE : APPLICATION ANDROID NFP 210 :Construction Rigoureuse des logiciels Elie Dagher 6601f Maha Dehayni 4996f Sous la direction de :M.Pascal Fares Septembre 2011 Tables des Matières Introduction

Plus en détail

Mise en route avec l'application mobile Android. Installation

Mise en route avec l'application mobile Android. Installation Mise en route avec l'application mobile Android L'application mobile SanDisk +Cloud vous permet d'accéder à votre contenu et de gérer votre compte depuis votre appareil portable. Grâce à l'application

Plus en détail

Mysql. Les requêtes préparées Prepared statements

Mysql. Les requêtes préparées Prepared statements Mysql Les requêtes préparées Prepared statements Introduction Les prepared statements côté serveur sont une des nouvelles fonctionnalités les plus intéressantes de MySQL 4.1 (récemment sorti en production

Plus en détail

Explorateur Windows EXPLORATEUR WINDOWS...1 INTRODUCTION...2 LANCEMENT DE L'EXPLORATEUR WINDOWS...3 PRÉSENTATION PHYSIQUE...3 RECHERCHER...

Explorateur Windows EXPLORATEUR WINDOWS...1 INTRODUCTION...2 LANCEMENT DE L'EXPLORATEUR WINDOWS...3 PRÉSENTATION PHYSIQUE...3 RECHERCHER... EXPLORATEUR WINDOWS SOMMAIRE EXPLORATEUR WINDOWS...1 INTRODUCTION...2 LANCEMENT DE L'EXPLORATEUR WINDOWS...3 PRÉSENTATION PHYSIQUE...3 RECHERCHER...6 ORGANISATION DE SES DOSSIERS...7 CRÉER UN DOSSIER...7

Plus en détail

Client Citrix ICA Windows CE Carte de référence rapide

Client Citrix ICA Windows CE Carte de référence rapide Client Citrix ICA Windows CE Carte de référence rapide Exigences Pour exécuter le client ICA Windows CE, vous devez disposer des éléments suivants : Un périphérique Windows CE Une carte d'interface réseau

Plus en détail

Warren PAULUS. Android SDK et Android x86

Warren PAULUS. Android SDK et Android x86 Android SDK et Android x86 2010/2011 Voici un petit tutoriel pour installer Android de façon à ce qu il soit compatible avec NetBeans et Eclipse, ainsi que l utilisation d Android x86. Ce tutoriel a été

Plus en détail

Documentation de CMS-gen

Documentation de CMS-gen Table des matières GÉNÉRALITÉ... 1 LA ZONE D'ADMINISTRATION... 2 LOGIN SUR LA ZONE D ADMINISTRATION... 2 EDITION DU CONTENU EN LIGNE... 3 LE MODE EDITION... 3 PUBLICATION... 3 SUPPRIMER DES MODIFICATIONS...

Plus en détail

Stockage de données sous Android

Stockage de données sous Android Stockage de données sous Android Master 2 informatique 2012-2013 Michel Chilowicz (sous licence CC By-NC-SA) Données temporaires d'une activité Une activité peut être détruite

Plus en détail

Rapport d'architecture

Rapport d'architecture Romain Alexandre Cécile Camillieri Rapport d'architecture 1 / 12 Table des matières I) Description du projet p. 3 1) Canaux de communication p. 3 2) Diagrammes de cas d'utilisation p. 3 II) Gestion des

Plus en détail

Documentation de l'application de gestion de courrier évolutive (G.E.D.) pour la Mairie de Voreppe

Documentation de l'application de gestion de courrier évolutive (G.E.D.) pour la Mairie de Voreppe Documentation de l'application de gestion de courrier évolutive (G.E.D.) pour la Mairie de Voreppe Tony Galmiche le 28 février 2011 (modifiée alb) Sommaire 1 - Accès au portail de l'application GED...3

Plus en détail