Pour chaque sujet, vous avez 3 tâches à accomplir, dont 2 travaux à
rendre. Vous êtes en binôme et vous devez tout rendre le 29 janvier
2018 (3 semaines).
Développer une petite application de test suivant les consignes
données dans votre sujet. Les applications de test doivent être en 2
langues (français et anglais préférées).
Attention quand votre application demande des permissions, il faut les
attribuer à la main (dans les préférences, menu Applications, selon
votre version d’Android).
Préparer une présentation de 20 min, là aussi en respectant les
consignes données dans votre sujet. Il s’agit en fait d’un mini-cours
pour expliquer aux autres étudiants comment faire ! Les présentations
auront lieu le 29 janvier à partir de 14h.
Android permet d’utiliser l’appareil photo directement. Cela permet
de prendre une photo sans lancer d’activité extérieure (une autre
solution pour prendre une photo est d’ailleurs de lancer l’activité
de prise de vue).
Pour cela vous utiliserez l’API Camera ». Elle permet de se connecter
à une des caméras de l’appareil (il y en a 1 ou 2), d’afficher une
*preview et de prendre une photo.
Développer une application capable de prendre une photo en utilisant
l’API Camera.
D’autre part la photo ne doit pas être enregistrée dans la galerie,
mais dans un dossier spécifique (à votre l’application).
Expliquer les 2 façons de prendre une photo : en utilisant l’activité
prise de vue (via un Intent) et en utilisant l’API Camera.
Il s’agit d’un composant d’application correspondant soit à une
opération n’ayant pas besoin d’interaction avec un utilisateur, soit
de fournir une fonctionnalité a d’autres applications. Un service n’a
donc pas d’écran associé (il est juste défini dans un fichier java et
n’a donc pas de fichier xml de layout).
L’exemple typique consiste à jouer un son : si le son est joué dans
une activité, lorsque l’on va sortir de l’activité le son cesse d’être
joué. L’utilisation d’un service va donc permettre de continuer à
jouer le son lorsqu’on aura changé d’activité ou qu’on aura éteint
l’écran.
Dans la pratique, une activité va démarrer le son. C’est sur cette
activité que se trouve l’interface utilisateur : boutons play/pause,
etc. Lorsque l’utilisateur appuie sur play, l’activité lance le
service qui va jouer le son. L’utilisateur peut alors sortir de
l’activité, utiliser d’autres activités ou même éteindre
l’écran. Lorsque que l’utilisateur revient vers l’activité initiale et
appuie sur stop, cette dernier envoie un message au service pour
arrêter le son.
Créer un service capable d’afficher des toasts régulièrement.
Expliquer ce qu’est un service, comment créer et lancer un service.
Android dispose de différentes de localisation (dont en particulier le
GPS, mais aussi la localisation via le réseau GSM). On peut utiliser
les données cartographiques de l’application Maps pour afficher une
carte sur l’écran.
Afficher la localisation, avec la précision. L’application devra
pouvoir mettre à jour la position régulièrement : un bouton permettra
d’activer ou de déactiver la mise à jour automatique de la
position. Une carte doit afficher la position du téléphone.
Expliquer le principe de la localisation sur Android, et comment aller
chercher la carte pour l’afficher dans votre application.
Les téléphones mobiles sont truffés de capteurs : GPS, accéléromètre, etc.
Le système permet de vérifier si les capteurs sont présents ou non, et
de faire différents réglages.
D’autre part l’écran tactile permet de reconnaître la position d’un
doigt. Les appareil récents sont capables de détecter plusieurs doigts
simultanés.
Développer une démo permettant de faire bouger une balle en fonction
de l’inclinaison de l’appareil : plus l’inclinaison est forte plus
la balle ira vite, dans le sens de l’inclinaison. Elle devra
rebondir sur les bords et aussi sur le doigt si l’utilisateur pose son
doigt sur l’écran (reconnaître au moins 1 doigt).
Expliquer comment vérifier la présence d’un capteur et comment
récupérer des données dessus.
Expliquer le fonctionnement de l’écran tactile.
Android permet de programmer des notifications qui vont apparaître sur
l’écran à un moment donné. Ces notifications peuvent signaler un
événement (par exemple la fin d’un morceau joué par le lecteur
multimédia), ou un rappel (de type alarme), ou encore signaler un
événement extérieur (reçu par le téléphone, comme une news par exemple).
Développer une application permettant d’afficher une notification à
une heure donnée (type rappel).
Présentez les différents types de notifications et indiquez comment
envoyer une notification.
Que souhaitons nous afficher sur la page d’accueil ? Quels sont les différents écrans ?
Quels sont les fonctionnalités qui vont nous manquer au point où nous en sommes dans le cours ?
En vous inspirant du cours, nous allons créer le contrat (classe) correspondant au schéma simple suivant (une seule table) :
À suivre :
Times.java, et dans la classe Times.
public Times(int hours, int mins) {
this(hours, mins, 0);
}
L’avantage d’utiliser l’autre constructeur, comme c’était déjà fait pour le constructeur de copie, est qu’on n’a pas à réécrire le code (évidemment dans cet exemple c’est très court, mais des constructeurs de classes plus complexes peuvent être très longs). De plus si on doit modifier, en cours de développement certains des arguments, on aura pas à reporter les mêmes modifications plusieurs fois, au risque d’en oublier. En règle générale il faut au maximum utiliser le même code pour les mêmes tâches.
public Times() {
java.time.LocalDateTime now = java.time.LocalDateTime.now();
this(now.getHour(), now.getMinute(), now.getSecond());
}
Or ce n’est pas possible car l’appel à un autre constructeur doit être la première instruction. La compilation donne :
tp1$ javac Times.java
Times.java:18: error: call to this must be first statement in constructor
this(now.getHour(), now.getMinute(), now.getSecond());
^
1 error
Il faut toujours lire soigneusement les messages d’erreur si on veut avoir une chance de corriger.
On peut contourner ce problème en créant une fonction (qu’on appellera par exemple initTimes, qui sera appelée par les constructeurs.
public void initTimes(int hours, int mins, int secs) {
this.hours = hours;
this.mins = mins;
this.secs = secs;
}
public Times() {
java.time.LocalDateTime now = java.time.LocalDateTime.now();
initTimes(now.getHour(), now.getMinute(), now.getSecond());
}
public Times(int hours, int mins, int secs) {
initTimes(hours, mins, secs);
}
public Times(int hours, int mins) {
initTimes(hours, mins, 0);
}
public Times(Times source) {
initTimes(source.hours, source.mins, source.secs);
}
Times en utilisant les différents constructeurs, puis on les affichera et on appellera les différentes méthodes à tester, en affichant à la fois les objets qui auront été modifiés. public static void main(String[] args) {
Times now=new Times();
System.out.println("it is "+now);
Times other=new Times(now);
System.out.println("other : "+ other);
if (now.equals(other))
System.out.println("les 2 objets contiennent la même chose");
else
System.out.println("les 2 objets contiennent des valeurs différentes");
other.addMinutes(1);
System.out.println("valeur de now: "+now);
System.out.println("valeur de other: "+other);
if (now.equals(other))
System.out.println("les 2 objets contiennent la même chose");
else
System.out.println("les 2 objets contiennent des valeurs différentes");
}
Exécution
tp1$ java Times it is 11:54:26 other : 11:54:26 les 2 objets contiennent la même chose valeur de now: 11:54:26 valeur de other: 11:55:26 les 2 objets contiennent des valeurs différentes
addMinutes
public static void main(String[] args) {
// ...
other.addMinutes(90);
System.out.println("other +90 min : "+ other);
// ...
Exécution
tp1$ java Times it is 12:1:58 other : 12:1:58 other +90 min : 12:91:58
On a pas pris en compte le fait que le nombre de minute ne peut dépasser 59 !
public void addMinutes(int n) {
this.mins+=n;
if (this.mins>=60) {
this.hours+=(this.mins/60);
this.mins=this.mins%60;
}
}
@Override
public String toString() {
String offsetM=mins<10?"0":"";
String offsetS=secs<10?"0":"";
return hours+":"+offsetM+mins+":"+offsetS+secs;
}
Exécution
tp1$ java Times it is 12:02:00 other : 12:02:00 other +90 min : 13:32:00
LocalDateTime. public boolean isBefore(Times other) {
return (this.hours<other.hours)
|| (this.mins < other.mins)
|| (this.secs < other.secs);
}
public boolean isAfter(Times other) {
return !isBefore(other)&&!equals(other);
}
public void addSecondes(int n) {
this.secs+=n;
if (this.secs>=60) {
this.addMinutes(this.secs/60);
this.secs=this.secs%60;
}
}
public void minuteur(int nbSecs) {
Times alarm=new Times(this);
alarm.addSecondes(nbSecs);
while (!alarm.equals(new Times())) {
//sleep(500);
}
System.out.println("Done! "+(new Times()));
}
sleep), nous verrons cela plus tard avec la notion de Thread.
Ficher : PremiersPas.java
public class PremiersPas {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Test d’Exécution
tp1$ ls PremiersPas.java tp1$ javac PremiersPas.java tp1$ ls PremiersPas.class PremiersPas.java tp1$ java PremiersPas Hello tp1$
Fichier Meteo.java
public class Meteo {
public static void main(String[] args) {
System.out.println("Hello");
System.out.print("Fait beau ? ");
String rep=Console.readln();
if (rep.equals("oui"))
System.out.println("Tant mieux");
else if (rep.equals("non"))
System.out.println("Espérons que ce soit mieux demain");
else
System.out.println("Je ne comprends pas !");
}
}
Test d’Exécution
tp1$ javac Meteo.java tp1$ ls Meteo.class Meteo.java PremiersPas.class PremiersPas.java tp1$ java Meteo Hello Fait beau ? oui Tant mieux tp1$ java Meteo Hello Fait beau ? bof Je ne comprends pas ! tp1$
Fichier Meteo.java
public class Meteo {
public static void main(String[] args) {
System.out.println("Hello");
System.out.print("Fait beau ? ");
String rep=Console.readln();
boolean fini=false;
while (!fini) {
if (rep.equals("oui") || rep.equals("o")) {
System.out.println("Tant mieux");
fini=true;
} else if (rep.equals("non") || rep.equals("n")) {
System.out.println("Espérons que ce soit mieux demain");
fini=true;
}
else {
System.out.println("merci de répondre par oui ou par non");
System.out.print("Fait beau ? ");
rep=Console.readln();
}
}
System.out.println("Caio");
}
}
Test d’Exécution
tp1$ javac Meteo.java tp1$ java Meteo Hello Fait beau ? bof merci de répondre par oui ou par non Fait beau ? non Espérons que ce soit mieux demain Caio tp1$]]>
1. Créez un projet Android dans eclipse (avec une activité de type BlankActivity). N’oubliez pas d’indiquez un nom de package (par exemple : fr.univ-paris8.master-handi.montp, les noms de package s’écrivent plutôt en minuscules).
2. Ouvrez l’éditeur graphique de layout. Insérer les widgets nécessaires pour notre convertisseur. Le TextView se trouve dans la palette Form Widgets (vous pouvez prendre un « Large »), et l’EditText dans la palette Text Fields.
4. Changez les étiquettes des 2 TextView et du bouton : click droit, menu « Edit Text… » en utilisant plutôt des « Strings » que du directement du texte (cliquez sur le bouton « New string », dans les 2 champs du haut de la boite de dialogue, insérez le texte et un ID.
1. Ouvrez le source java (dans le navigateur de projet : cherchez le dossier « src », puis trouvez le code de l’activité (par défaut MainActivity.java sauf si vous l’avez changé dans l’assistant de création de projet).
2. Créez des attributs pour garder une référence sur les 2 champs de texte :
private EditText champTextDecimal, champTextBinaire;
et les associer aux éléments du layout grâce à leurs ID (dans la méthode onCreate) :
champTextDecimal=(EditText) findViewById(R.id.editTextDec);
Ici l’ID a été modifié (l’ID par défaut est « edittext1 »). On trouve les ID par un click droit sur l’objet correspondant dans l’éditeur graphique d’écrans (menu « Edit Id »).
3. Toujours dans la méthode onCreate, déclarer la classe courante comme listener du bouton. Cela nous permettra de traiter les événements en provenance du bouton.
Button b=(Button) this.findViewById(R.id.buttonConv); b.setOnClickListener(this);
Il n’est pas nécessaire de garder la référence au bouton en attribut (sauf si on a besoin de changer fréquemment le texte présent sur le bouton).
Maintenant il faut que la classe courante (MainActivity) implémente l’interface OnClickListener, et donc implémenter la méthode :
public void onClick(View v)
.
Dans un premier temps on enverra simplement un « toast« , c’est à dire un message temporaire à l’utilisateur :
Toast.makeText( getApplicationContext(),
"texte du message",
Toast.LENGTH_SHORT).show();
4. Implémentez la méthode de conversion : allez chercher le texte saisi dans le champs correspondant (attention la méthode getText() de EditText ne renvoie pas directement un String, il faut le convertir, par exemple, en utilisant « toString()« , puis convertir cette chaîne en entier. On utilisera la méthode « setText(…) » pour placer le résultat dans le champ de saisie correct.
5. On remarque que cette application ne fonctionne que dans un sens. Ajoutez un second bouton pour faire la conversion inverse, et implémentez la. Maintenant que nous aurons 2 boutons, la méthode de traitement des événements (onClick) doit faire la différence entre les 2. Pour cela on utilisera la méthode getId() de l’objet View passé en argument, et on le comparera à l’ID du bouton.
6. Cherchez sur Internet la façon de se passer des boutons de conversion : comment faire pour que la conversion se fasse en temps réel, à chaque fois qu’on entre un chiffre dans le champs de saisie.
]]>1. Créez un projet approprié dans eclipse, puis une interface comportant un TextView dans lequel on écrira le résultat – la date envoyée par le serveur (ou un message d’erreur), et un bouton permettant de lancer une requête sur le serveur.
Attention, le système Android nécessite dispose d’un mécanisme de permission, censé prévenir l’utilisateur lorsque des opérations sensibles, telles que l’accès au réseau ou à la mémoire de masse, ou l’accès aux capteurs, sont réalisées. Notre application doit utiliser un service réseau, il faut donc lui en donner la permission dans le fichier AndroidManifest.xml (à trouvez dans le projet). Insérez la ligne suivante après celle qui définit les sdk utilisés.
<uses-permission android:name="android.permission.INTERNET"/>
2. Implémentez le traitement du bouton de requête (reprenez le code du client réalisé lors du TP 2).
Que se passe-t-il ?
3. Si cela n’a pas marché, c’est parce que le système Android impose que les appels réseau soient passés dans un thread séparé du thread principal. Modifiez votre code pour déplacer l’ouverture du socket, la lecture et sa fermeture dans un thread séparé. Que se passe-t-il maintenant ?
4. Vous devez trouver une exception de type « android.view.ViewRoot$CalledFromWrongThreadException » avec le message « Only the original thread that created a view hierarchy can touch its views.« . Cela signifie que le thread qui gère le socket ne peut pas écrire dans l’interface graphique, qui est gérée par un autre Thread. On va devoir utiliser un système d’envoi de message. Le principe est de créer un objet de type handle qui va faire l’intermédiaire entre les 2. Le thread qui gère les socket va envoyer un message par l’intermédiaire de ce handle, et celui ci va le renvoyer au thread qui gère l’interface graphique.
Pour cela on va déclare un objet handler, en surchargeant la méthode handleMessage de la classe Handler afin de lui faire réaliser le traitement voulu (ici afficheur.setText(text);).
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String text = (String)msg.obj;
afficheur.setText(text);
}
};
Ensuite lorsque dans le thread qui gère le socket on va utiliser :
Message msg = new Message(); msg.obj = answer; mHandler.sendMessage(msg);
On crée ici un message, on y copie la chaîne à transmettre, et on envoie le message au handler.
Voici le code complet de la classe :
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.graphics.Color;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class GetDateAppActivity extends Activity implements OnClickListener, Runnable {
private static int DEFAULTPORT = 9090;
private static String DEFAULTSERVER = "ouessant.univ-paris8.fr";
private int portNumber;
private String serverName;
private TextView afficheur;
private Handler mHandler;
private EditText serverField, portField;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.getdateapp_activity);
portNumber=DEFAULTPORT;
serverName=DEFAULTSERVER;
Button t=(Button) this.findViewById(R.id.button1);
t.setTextColor(Color.RED);
t.setOnClickListener(this);
afficheur=(TextView) this.findViewById(R.id.textView1);
serverField=(EditText)this.findViewById(R.id.editText1);
portField=(EditText)this.findViewById(R.id.editText2);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String text = (String)msg.obj;
afficheur.setText(text);
}
};
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
public void onClick(View v) {
if (v.getId()==R.id.button1) {
this.serverName=this.serverField.getText().toString();
this.portNumber=Integer.parseInt(this.portField.getText().toString());
new Thread(this).start();
}
}
@Override
public void run() {
Socket s=null;
try {
s = new Socket(serverName, portNumber);
} catch (IOException e) {
//afficheur.setText("Cannot connect to "+server+" on port "+port);
Message msg = new Message();
msg.obj = "Cannot connect to "+serverName+" on port "+portNumber;
mHandler.sendMessage(msg);
}
if (s!=null) {
BufferedReader input;
try {
input = new BufferedReader(new InputStreamReader(s.getInputStream()));
String answer = input.readLine();
//afficheur.setText(answer);
Message msg = new Message();
msg.obj = answer;
mHandler.sendMessage(msg);
input.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
]]>