Conférence donnée à la Droidcon Tunisia 2015.
Les mises à jour OTA sont partout sur nos téléphones (ok, presque partout) mais comment fonctionnent-elles ?
Au cours de cette présentation, nous verrons :
Ce qu’est une MàJ OTA
Comment l’implémenter (exploration de code : intent, permission système, recovery, etc.)
Quelle architecture backend pour mettre en place une solution d’OTA
2. 2
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
4. 4
QUI SOMMES NOUS ?
Nos sujets :
● Applicatif métier mobile
● Gestion de parc
● Gestion applicative
● Objets connectés
● OS Embarqués
Nos compétences :
● Étude, conseil et analyse
● Gestion de projets
● Développement et recueil
des retours utilisateurs
● Déploiement
● Mise en marché
6. 6
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
7. QU’EST-CE QU’UNE OTA ?
7
Il faut en réalité parler de
Firmware Over The Air (FOTA).
Cette opération permet de mettre
à jour des terminaux à
distance, sans que l’utilisateur n’ait
à les connecter sur un ordinateur.
La mise à jour est stockée sur un
serveur que le téléphone vient
télécharger puis installer.
8. 8
Les raisons de lancer une OTA vers un parc de terminaux sont multiples :
Sécurité Bugs
Time to
market
Android
QU’EST-CE QU’UNE OTA ?
Service après
vente
Fonctionnalités
9. 9
Mais elle présente des risques, contrairement à une installation
classique par USB :
Batterie Consommation
data
Corruption des
fichiers
QU’EST-CE QU’UNE OTA ?
10. 10
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
12. ARCHITECTURE DU SYSTÈME
12
Recovery (ou recovery OS)
C’est un OS minimal qui est
spécialisé dans l’exécution de tâches
qu’Android directement ne peut pas
faire :
• réinitialisation du terminal
• installer des mises à jour
Il peut être lancé manuellement (combinaisons de touches) ou via adb :
adb reboot recovery
13. 13
Recovery (ou recovery OS)
Selon l’Android Compatibility Definition
Document, le terminal doit intégrer “un
mécanisme pour remplacer le système
dans sa globalité”
“… il doit supporter des mises à jour sans
supprimer les données utilisateur”
ARCHITECTURE DU SYSTÈME
14. 14
Commandes du Recovery
Au lancement, le recovery détecte si le fichier /cache/recovery/command existe.
Si tel est le cas, il exécutera les commandes les unes à la suite des autres.
-‐-‐update-‐package=<path>
-‐-‐wipe-‐data
-‐-‐wipe-‐cache
-‐-‐locale
-‐-‐send-‐intent=...
Fichier à vérifier puis installer
Effacer les partitions userdata et cache
Effacer la partition cache
Langue pour l’interface utilisateur
Envoyer cet Intent lorsqu’Android sera redémarré
ARCHITECTURE DU SYSTÈME
15. 15
Interaction Bootloader-Recovery
Pour que le bootloader comprenne qu’il faut lancer le recovery lorsqu’il y a des
commandes, il copie ses arguments vers le bootloader control block (BCB).
Le format du BCB est défini dans la structure bootloader_message :
struct bootloader_message {
char command[32]; char stage[32];
char status[32]; char reserved[224];
char recovery[768];
}
ARCHITECTURE DU SYSTÈME
16. 15
Interaction Bootloader-Recovery
Pour que le bootloader comprenne qu’il faut lancer le recovery lorsqu’il y a des
commandes, il copie ses arguments vers le bootloader control block (BCB).
Le format du BCB est défini dans la structure bootloader_message :
struct bootloader_message {
char command[32]; char stage[32];
char status[32]; char reserved[224];
char recovery[768];
}
La commande pour le bootloader
boot-recovery
ARCHITECTURE DU SYSTÈME
17. 15
Interaction Bootloader-Recovery
Pour que le bootloader comprenne qu’il faut lancer le recovery lorsqu’il y a des
commandes, il copie ses arguments vers le bootloader control block (BCB).
Le format du BCB est défini dans la structure bootloader_message :
struct bootloader_message {
char command[32]; char stage[32];
char status[32]; char reserved[224];
char recovery[768];
}
Options pour le recovery
--update
ARCHITECTURE DU SYSTÈME
18. 15
Interaction Bootloader-Recovery
Pour que le bootloader comprenne qu’il faut lancer le recovery lorsqu’il y a des
commandes, il copie ses arguments vers le bootloader control block (BCB).
Le format du BCB est défini dans la structure bootloader_message :
struct bootloader_message {
char command[32]; char stage[32];
char status[32]; char reserved[224];
char recovery[768];
}
Etape de l’installation
(si des redémarrages sont nécessaires)
ARCHITECTURE DU SYSTÈME
19. 16
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
20. QUE CONTIENT UNE OTA ?
17
Génération du fichier
AOSP comporte un script python nommé
ota_from_target_files qui génère
automatiquement un fichier zip installable.
Par défaut, il crée une mise à jour complète.
Mais il peut aussi produire une mise à jour
différentielle en lui donnant la dernière OTA.
21. 18
Tronc common
META-‐INF
CERT.RSA
CERT.SF
com
android
metadata
otacert
google
android
update-‐binary
updater-‐script
MANIFEST.MF
QUE CONTIENT UNE OTA ?
22. 18
Tronc common
META-‐INF
CERT.RSA
CERT.SF
com
android
metadata
otacert
google
android
update-‐binary
updater-‐script
MANIFEST.MF
Fichier le plus important
QUE CONTIENT UNE OTA ?
23. 19
Updater-script
Fichier listant des commandes à exécuter :
apply_patch
apply_patch_check
delete / delete_recursive
file_getprop
format
mount / umount
package_extract_dir / package_extract_file
set_metadata / set_metadata_recursive
show_progress
symlink
ui_print
QUE CONTIENT UNE OTA ?
24. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
QUE CONTIENT UNE OTA ?
25. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Monter une partition
QUE CONTIENT UNE OTA ?
26. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Obtenir une propriété système dans
un fichier contenant des propriétés
QUE CONTIENT UNE OTA ?
27. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Obtenir une propriété du système
QUE CONTIENT UNE OTA ?
28. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Application d’un patch sur un fichier
ou une partition avec vérification des
hashs sur les fichiers
QUE CONTIENT UNE OTA ?
29. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Appliquer sur un fichier ou un
dossier des droits de manière
récursive (utilisateur/groupe/
SELinux…)
QUE CONTIENT UNE OTA ?
30. 20
mount("ext4", "EMMC", "/dev/block/platform/dw_mmc.0/by-‐name/system", “/system");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.3/
KTU84L/1148727:user/release-‐keys" ||
file_getprop("/system/build.prop", "ro.build.fingerprint") == "google/mantaray/manta:4.4.4/
KTU84P/1227136:user/release-‐keys" ||
abort("Package expects build fingerprint of google/mantaray/manta:4.4.3/KTU84L/
1148727:user/release-‐keys or google/mantaray/manta:4.4.4/KTU84P/1227136:user/release-‐keys; this
device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "manta" || abort("This package is for "manta" devices; this
is a "" + getprop("ro.product.device") + "".");
ui_print("Verifying current system...");
show_progress(0.100000, 0);
apply_patch_check("/system/app/BasicDreams.apk", "42b5544ed0a896f536c1d58ffe1c68a974f12687",
"67280e2672c034505ba417e6c0054be375415c08") || abort(""/system/app/BasicDreams.apk" has
unexpected contents.");
set_progress(0.000065);
set_metadata_recursive("/system/xbin", "uid", 0, "gid", 2000, "dmode", 0755, "fmode", 0755,
"capabilities", 0x0, "selabel", "u:object_r:system_file:s0");
ui_print("Patching remaining system files...");
apply_patch("/system/build.prop", "-‐",
d7978a543a5677b77f7c1b15b5f583fb8fa14bd8, 2774,
4d220efc37eb1786bbb379ae31145bf7edd176d9, package_extract_file("patch/system/
build.prop.p"));
set_metadata("/system/build.prop", "uid", 0, "gid", 0, "mode", 0644, "capabilities", 0x0);
unmount("/system");
Démonter un volume
QUE CONTIENT UNE OTA ?
31. 21
Signature des fichiers OTA
Les applications sont signées grâce à l’outil signapk.
Les fichiers d’OTA sont signés avec le même utilitaire, mais avec une
option spéciale (-w) qui permet de signer le fichier dans sa globalité
et non chacun d’entre eux.
Cette signature est ensuite vérifiée à deux reprises :
•Lorsqu’Android va écrire dans le fichier de commande du recovery
•Dans le recovery, avant de flasher l’image
Le dossier META-INF contient notamment le certificat de mise à jour
(au format PEM). Il est possible/conseillé d’utiliser une clé différente de
celles utilisées dans le reste du système.
QUE CONTIENT UNE OTA ?
32. 22
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
33. IMPLÉMENTATION
23
Côté Android
Plusieurs étapes vont se succéder afin d’appliquer une mise à jour :
S’interfacer dans le menu
Paramètres
Détecter la présence d’une
mise à jour
Télécharger la mise à jour Installer la mise à jour
Supprimer la mise à jour, une
fois installée
34. 24
S’interfacer dans l’application
Paramètres
A partir du moment où les Google Play Services
sont installés, un écran de mise à jour est
forcément disponible dans les Paramètres.
IMPLÉMENTATION
35. 25
S’interfacer dans l’application Paramètres
private static final String KEY_SYSTEM_UPDATE_SETTINGS =
"system_update_settings";
IMPLÉMENTATION
36. 25
S’interfacer dans l’application Paramètres
if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
Utils.updatePreferenceToSpecificActivityOrRemove(act,
parentPreference,
KEY_SYSTEM_UPDATE_SETTINGS,
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
} else {
// Remove for secondary users
removePreference(KEY_SYSTEM_UPDATE_SETTINGS);
}
private static final String KEY_SYSTEM_UPDATE_SETTINGS =
"system_update_settings";
IMPLÉMENTATION
37. 26
S’interfacer dans l’application Paramètres
Intent intent = preference.getIntent();
if (intent != null) {
// Find the activity that is in the system image
PackageManager pm = context.getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
int listSize = list.size();
for (int i = 0; i < listSize; i++) {
ResolveInfo resolveInfo = list.get(i);
if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
!= 0) {
// Replace the intent with this specific activity
preference.setIntent(new Intent().setClassName(
resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name));
if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
// Set the preference title to the activity's label
preference.setTitle(resolveInfo.loadLabel(pm));
}
return true;
}
}
}
IMPLÉMENTATION
38. 26
S’interfacer dans l’application Paramètres
Intent intent = preference.getIntent();
if (intent != null) {
// Find the activity that is in the system image
PackageManager pm = context.getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
int listSize = list.size();
for (int i = 0; i < listSize; i++) {
ResolveInfo resolveInfo = list.get(i);
if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
!= 0) {
// Replace the intent with this specific activity
preference.setIntent(new Intent().setClassName(
resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name));
if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
// Set the preference title to the activity's label
preference.setTitle(resolveInfo.loadLabel(pm));
}
return true;
}
}
}
Le nom de l’application sera celui
affiché dans les Paramètres.
IMPLÉMENTATION
39. 27
S’interfacer dans l’application
Paramètres
Il faut donc déclarer une Activity avec cet Intent-
Filter :
<intent-‐filter android:priority="999">
<action android:name="android.settings.SYSTEM_UPDATE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-‐filter>
Il faut utiliser une priorité plus
élevée que l’Activity des Play
Services
IMPLÉMENTATION
40. 28
Détecter la présence d’une mise à jour
Requête faite en tâche de fond (Service) et notification à l’utilisateur.
IMPLÉMENTATION
41. 29
Détecter la présence d’une mise à jour
Quelques attributs utiles :
Build.TIME : retourne l’heure de build de la ROM
1424637868000
Build.DISPLAY : retourne le nom de la ROM (côté utilisateur)
VITAMIN_A_V1.0.1_beta2
Build.FINGERPRINT : retourne le nom complet de la ROM
Vitamin/VitaminA/VitaminA:4.4.4/KTU84P/20150222.214246:user/release-keys
IMPLÉMENTATION
42. 30
Télécharger l’OTA
L’emplacement du fichier et son nom n’ont pas
d’importance.
On peut se servir du DownloadManager pour
cette tâche, mais il pourra uniquement
télécharger dans les répertoires publics.
IMPLÉMENTATION
44. 32
Télécharger l’OTA
On est ensuite notifié via un Intent de la fin du téléchargement :
<receiver android:name=".DownloadReceiver">
<intent-‐filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-‐filter>
</receiver>
IMPLÉMENTATION
45. 32
Télécharger l’OTA
On est ensuite notifié via un Intent de la fin du téléchargement :
<receiver android:name=".DownloadReceiver">
<intent-‐filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-‐filter>
</receiver>
Il ne faut pas oublier de vérifier que le fichier est valide.
IMPLÉMENTATION
46. 33
Lancer la mise à jour
Une fois le fichier à disposition, il faut écrire les commandes pour que le
recovery puisse l’installer.
Il n’y a pas besoin de le faire à la main, car la classe RecoverySystem (API 8) s’en
occupe. Plusieurs méthodes sont disponibles :
public static void installPackage (Context context, File packageFile)
public static void rebootWipeCache (Context context)
public static void rebootWipeUserData (Context context)
public static void verifyPackage (File packageFile,
RecoverySystem.ProgressListener listener, File deviceCertsZipFile)
IMPLÉMENTATION
47. 34
Lancer la mise à jour
public static void installPackage(Context context, File packageFile) throws IOException {
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
final String filenameArg = "-‐-‐update_package=" + filename;
final String localeArg = "-‐-‐locale=" + Locale.getDefault().toString();
bootCommand(context, filenameArg, localeArg);
}
IMPLÉMENTATION
48. 35
Lancer la mise à jour
private static void bootCommand(Context context, String... args) throws IOException {
RECOVERY_DIR.mkdirs(); // In case we need it
COMMAND_FILE.delete(); // In case it's not writable
LOG_FILE.delete();
FileWriter command = new FileWriter(COMMAND_FILE);
try {
for (String arg : args) {
if (!TextUtils.isEmpty(arg)) {
command.write(arg);
command.write("n");
}
}
} finally {
command.close();
}
// Having written the command file, go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
throw new IOException("Reboot failed (no permissions?)");
}
IMPLÉMENTATION
49. 36
Lancer la mise à jour
Il faut donc laisser le système faire, sachant que la permission de reboot doit
être demandée, tout comme celle pour écrire sur la partition du cache :
<uses-‐permission android:name="android.permission.REBOOT" />
<uses-‐permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
IMPLÉMENTATION
51. 38
Supprimer le fichier d’OTA
Le recovery ne supprime pas le fichier de mise à jour, il faut donc le faire à la
main. On ajoute pour cela un Intent-Filter :
<receiver android:name=".BootReceiver">
<intent-‐filter>
<action android:name="android.intent.action.PRE_BOOT_COMPLETED" />
</intent-‐filter>
</receiver>
IMPLÉMENTATION
52. 38
Supprimer le fichier d’OTA
Le recovery ne supprime pas le fichier de mise à jour, il faut donc le faire à la
main. On ajoute pour cela un Intent-Filter :
<receiver android:name=".BootReceiver">
<intent-‐filter>
<action android:name="android.intent.action.PRE_BOOT_COMPLETED" />
</intent-‐filter>
</receiver>
… où l’on vérifie que le terminal dispose bien de la nouvelle ROM
IMPLÉMENTATION
53. 39
PLAN
1. Qui sommes nous ?
2. Qu’est-ce qu’une OTA ?
3. Architecture du système
4. Que contient une OTA ?
5. Comment l’implémenter ?
6. Partie Backend
54. BACKEND
40
La version basique
• Placer les fichiers à disposition des terminaux Android sur le serveur
• Ecrire un JSON à la main qui liste les fichiers d’OTA et les infos utiles (date de
build, etc.)
• C’est tout !
55. BACKEND
41
La version industrielle
• Doit pouvoir permettre de placer les fichiers à disposition des terminaux
Android sur le serveur
• Doit fournir une API aux terminaux Android afin de leur servir la bonne OTA
• Doit pouvoir gérer plusieurs types de terminaux
• Ainsi que plusieurs branches de distribution
• Doit nous permettre d’avoir des statistiques sur le parc géré
60. 46
BACKEND
Pour résumer
On a donc :
•Des terminaux
•Qui sont regroupés en une flotte selon leur modèle
•Et qui peuvent être à différentes versions du software
Et tout ça, c’est le terminal lui-même qui nous le dit
63. 49
BACKEND
Ca ne suffit pourtant pas !
Quand on gère un parc conséquent de terminaux Android, on veut pouvoir
tester l’OTA sur un échantillon avant de la proposer à tout le monde.
On veut donc pouvoir créer des branches de distribution.
Cela permet :
• d’isoler des terminaux (téléphones de développement, modèles pas encore
sortis, tablettes déposées au SAV)
• de distribuer une OTA à une sous-partie du parc de téléphone