Introduction FAQ Glossary Project TD 1 TD 2 TD 3 TD 4 TD 5 TD 6 TD 7 TD 8
Retour au cours.
Objectifs :
Le but de ce TD est de manipuler un périphérique d'Android, à la fois à partir du shell et à partir d'un programme Java. Ce périphérique, le frame-buffer, permet d'écrire directement dans la mémoire écran de l'ordinateur, pour effectuer toutes sortes d'opérations visuelles.
Nous allons commencer par quelques manipulations simples du frame-buffer depuis le shell.
Le frame-buffer d'Android peut être lu et écrit à partir du
fichier périphérique /dev/graphics/fb0
.
Il contient deux copies de l'écran, entre lesquelles le système alterne : ceci permet de modifier une copie pendant que l'autre est affichée, puis d'afficher d'un coup la copie modifiée. Cette méthode évite les effets visuels désagréables quand des modifications sont effectuées directement sur la copie visible.
La couleur de chaque pixel (point sur l'écran) est codé sur deux octets, et chaque ligne contient 320 pixels.
Comme d'habitude, nous allons lancer l'émulateur et nous y connecter.
emulator @inf422 & adb forward tcp:4444 tcp:23 telnet 127.0.0.1 4444
Copier le contenu du fichier /dev/zero
dans le
frame-buffer. Que se passe-t'il ?
Copier le contenu du fichier /dev/urandom
dans le
frame-buffer. Que se passe-t'il ?
Appuyer sur la touche Menu
du téléphone pour rétablir
l'affichage.
Au cours de ce TD, le processus system_server
va nous
gêner légèrement. En effet, ce processus est en charge, entre autres,
de :
system_server
ne
fonctionne pas correctement.
Copier le contenu du frame-buffer dans un fichier
/data/snap1.img
. En déduire le nombre de lignes affichées
à l'écran.
En appuyant sur diverses touches du téléphone, faire des copies du contenu du frame-buffer dans plusieurs fichiers pour des écrans différents.
Copier maintenant chacun de ces fichiers dans le frame-buffer et visionner les résultats.
STOP
et CONT
Deux signaux jouent un rôle très important dans la gestion des processus, et sont utilisés notoirement pas les shells :
STOP
envoyé à un processus bloque
complètement l'exécution de ce processus. Ce signal est envoyé par
exemple quand on utilise CTRL-z
dans un terminal.CONT
envoyé à un processus bloqué
débloque ce processus, qui reprend donc son exécution. Ce signal est
envoyé par exemple quand on utilise les commandes fg
et
bg
après un CTRL-z
.Pour utiliser ces signaux, il suffit d'utiliser les commandes
habituelles kill
et killall
avec en premier
argument -STOP
ou -CONT
.
Utiliser les signaux STOP
et CONT
pour
bloquer et débloquer alternativement le processus
system_server
.
Modifier le frame-buffer et vérifier que, quand
system_server
est bloqué, l'affichage de l'écran
correspond exactement à celui du frame-buffer.
Télécharger les fichiers smiley.sh
et colors.sh
, et copier les sur
Android :
adb push smiley.sh /home/smiley.sh adb push colors.sh /home/colors.sh
Lancer les scripts /home/smiley.sh
et
/home/colors.sh
dans Android.
Quels sont les droits d'accès attachés au périphérique frame-buffer ? Modifier ces droits si nécessaire pour que n'importe quel utilisateur puisse modifier le frame-buffer.
Sous Android, les programmes Java ne sont pas exécutés par
l'utilisateur root
, mais par de faux utilisateurs. Ceci
permet de restreindre leurs actions, et de diminuer les risques en
matière de sécurité.
Par ailleurs, les programmes Java ne sont pas autorisés à lancer des commandes Unix sur Andoid.
Dans la suite du TD, il sera important que notre application Java
soit capable de bloquer et débloquer le processus
system_server
. Cependant, l'envoi d'un signal a un
processus n'est autorisé que s'il provient du même utilisateur que
celui qui a lancé le processus, ou de root
. L'application
Java que nous allons écrire ne pourra donc pas directement interagir
avec system_server
.
Pour résoudre ce problème, nous allons utiliser l'architecture suivante:
root
depuis le shell, il
attend les connexions sur le port 7000, et bloque le processus
system_server
quand une connexion est reçue.root
depuis le shell, il
attend les connexions sur le port 7001, et débloque le processus
system_server
quand une connexion est reçue. Écrire un script shell /home/stop.sh
qui bloque le
processus system_server
et ouvre l'accès en écriture au
frame-buffer à tous les utilisateurs.
Écrire un script /home/cont.sh
qui débloque le
processus system_server
et rétablit les droit d'accès
originaux au frame-buffer.
Utiliser la commande nc
pour lancer les deux
serveurs. Modifier les deux scripts pour qu'une ligne ok
soit retournée à chaque connexion.
Télécharger les sources de l'application TD7.
Nous avons prédéfini un certain nombre de fonctions pour vous :
mythread()
doit être exécutée dans un thread secondaire, et effectue les opérations suivantes : connexion au port 7000, appel de la fonction principale mymain()
, puis connexion au port 7001.
mysleep(nsecs)
permet de mettre le thread en attente pendant nsecs
secondes.log(line : String)
ajoute la ligne
line
dans le fichier
/data/data/com.android.td7/log.txt
.
logexc(String fun, Exception exn )
ajoute une ligne
dans le fichier /data/data/com.android.td7/log.txt
indiquant que l'exception exn
a été attrapée dans la
fonction fun
.
connect(port)
doit ouvrir une connexion locale vers 127.0.0.1:port et lire la réponse du serveur sur une ligne.onCreate()
doit créer un thread pour exécuter mythread()
.mymain()
doit contenir les appels aux fonctions d'affichage que vous voulez exécuter dans votre programme. La fonction connect
est utilisée pour interagir avec les deux serveurs mis en place dans la question précédente.
Compléter la fonction connect(port)
pour :
La fonction mythread()
contient le coeur du
programme. Elle doit être exécutée dans un thread différent du thread
principal, puisque ce dernier, qui a la charge de l'interface
graphique Android, sera bloqué lorsqu'il interagit avec le processus
system_server
que l'on a stoppé au début de
l'exécution.
Compléter la fonction onCreate()
pour créer et exécuter un thread dont le seul rôle est d'appeler la fonction mythread()
.
FileOutputStream
La classe FileOutputStream
permet d'écrire des données
directement dans un fichier. Ceci peut s'avérer utile si on ne veut
pas que des conversions soient effectuées automatiquement (conversions
entre codages différents suivant les pays par exemple).
FileOutputStream(String file_name)
est le constructeur qui permet d'obtenir un FileOutputStream
pointant sur le fichier file_name
. outstream.write(bytes, pos, len)
permet d'écrire len
octets, à partir de la position pos
du tableau bytes
(de type byte[]
).outstream.flush()
permet de forcer le vidage du tampon en écrivant les données dans le fichier.outstream.close()
permet de fermer le fichier, en vidant le tampon auparavant.Dans l'application TD7, nous avons défini certains paramètres de
l'écran (dimensions, couleurs principales), ainsi qu'un tableau
screen
(de type byte[]
) de la taille exacte
de la mémoire écran.
Définir une fonction syncScreen()
qui affiche le
contenu de screen
sur l'écran du téléphone. Pour cela, on
copiera deux fois à la suite le contenu du tableau
screen
dans le frame-buffer.
Chaque pixel (point de l'écran) est représenté par deux octets
dans le tableau screen
.
Java, comme de nombreux languages de programmation, définit plusieurs types d'entiers suivant le nombre d'octets utilisés pour les encoder, et les plages de valeurs qu'ils peuvent prendre:
byte
: entier positif sur un octet [0 : 255]
char
: entier sur un octet [-128 : 127]
short
: entier sur deux octets
int
: entier sur quatre octets
Il est possible de changer le type d'un entier, si celui-ci est bien conforme à la plage de valeurs du nouveau type:
int x = 56; char y = (char)x;
Définir une fonction colorPixel(int x, int y, int
color)
qui modifie les deux octets du pixel (x,y) pour lui
donner la couleur color
. On pourra utiliser l'opérateur
modulo %
ou le et binaire &
.
Définir une fonction colorScreen(int color)
qui
remplit tout l'écran avec la même couleur color
.
Chaque couleur est codée sur deux octets, au format RGB en 5:5:5:1 bits, le bit de poids faible n'étant pas utilisé.
Définir une fonction color(int red, int green, int
blue)
qui retourne un entier encodant une couleur, à partir
de ses composantes red
, green
et
blue
sur 5 bits (i.e. dans [0:31]).
Tester cette fonction avec colorScreen
en coloriant
l'écran en rouge, vert, bleu, blanc, noir et jaune.
Définir une fonction colorLine(int x0, int y0, int x1, int y1, int
color)
qui colorie tous les points sur une ligne entre (x0,y0) et (x1,y1).
On pourra utiliser des types float
pour améliorer la qualité de la ligne.
Dans cette partie, nous allons lire un snapshot depuis un fichier
et l'écrire dans le tableau screen
.
FileInputStream
La classe FileInputStream
permet de lire des données
directement depuis un fichier.
FileInputStream(String file_name)
est le
constructeur qui permet d'obtenir un FileInputStream
pointant sur le fichier file_name
. instream.read(bytes, pos, len)
permet de lire au
maximum len
octets depuis le fichier, et de les écrire
à partir de la position pos
dans le tableau
bytes
. Le nombre d'octets lus est retourné comme
résultat de l'appel.instream.close()
permet de fermer le fichier.Le paramêtre len
de la méthode read
de
FileInputStream
n'indique pas le nombre exact d'octets
lus, mais la longueur maximale lue. Il peut être pratique de
disposer d'une fonction plus prévisible.
Définir une fonction reallyInput(FileInputStream in, byte[]
bytes, int pos, int len)
qui retourne uniquement quand
len
octets ont été lus depuis le fichier et placés dans
bytes
à la position pos
.
Nous pouvons maintenant utiliser cette fonction pour lire tout un écran à la fois.
Définir une fonction readSnapshot(String file_name)
qui lit le premier écran placé dans le fichier file_name
et le copie dans screen
.
Utiliser cette fonction pour afficher le contenu du fichier /data/snap1.img
.
Nous allons maintenant utiliser les primitives précédentes pour afficher des informations sur le système de fichiers. En particulier, nous serions intéressés par connaître la distribution des tailles des fichiers sur le système Android.
Pour cela, nous allons tracer sur l'écran la courbe suivante: en abscisse, les tailles des fichiers (on pourra commencer par une échelle de 1 pixel = 1 Koctect); en ordonnée, le pourcentage de fichiers dont la taille est inférieure à la valeur en abscisse. Ainsi, un point sur la courbe de coordonnées (x,y) indique que y% des fichiers ont une taille inférieure à x Ko. Ce type de courbe est appelée une Cumulative Distribution Function.
Définir une fonction readDirectory(String dir, int unit, int[]
nfiles)
, dont les arguments sont le nom d'un répertoire, une unité
d'échelle et un tableau d'entiers. Ce tableau d'entiers contient, pour
chaque indice i, le nombre de fichiers trouvés dont la taille est
comprise entre i*unit et (i+1)*unit. La fonction
readDirectory
doit mettre à jour ce tableau à partir du
contenu du répertoire dir.
En appelant la fonction readDirectory
à partir de la
racine du système de fichier, nous pouvons maintenant collecter la
distribution des tailles de fichiers.
Ecrire la fonction dist2CDF(int[] dist)
qui transforme le
tableau de distribution précédent en un tableau de CDF.
Nous avons maintenant obtenu le tableau qu'il nous reste à afficher sur l'écran du téléphone.
Ecrire la fonction displayCDF()
qui affiche le tableau de
la CDF sur l'écran. Interpréter le résultat.
Vous pouvez maintenant vous amuser à afficher d'autres statistiques sur le système de votre téléphone.