Solution d'OTA
-
Upload
sidereo -
Category
Technology
-
view
388 -
download
0
Transcript of Solution d'OTA
SOLUTION D’OTA
Pierre-Olivier Dybman Edouard Marquez
+PierreOlivierDybman (aka flappy beard)
+EdouardMarquez (aka g123k)
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
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
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
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
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
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
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
ARCHITECTURE DU SYSTÈME
11
Bootloader
Programme de bas niveau qui est exécuté au démarrage du terminal.
Il s’occupe d’initialiser le matériel puis de lancer le système d’exploitation.
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
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
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
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
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
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
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
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
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.
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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 ?
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
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
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
25
S’interfacer dans l’application Paramètres
private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
IMPLÉMENTATION
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
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
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
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
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
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
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
31
Télécharger l’OTADownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request requestDownload = new DownloadManager.Request(Uri.parse(update.url)) .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) .setVisibleInDownloadsUi(false) .setTitle(update.buildNumber) .setDestinationUri(getDestinationUri()) .setDescription(update.buildNumber);
return dm.enqueue(requestDownload);
<uses-‐permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-‐permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" /> <uses-‐permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
Et demander ces permissions :
IMPLÉMENTATION
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
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
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
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
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
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
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
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
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
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 !
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é
45
BACKEND
Imaginons :
Et ils n’ont pas tous la même version de la ROM
Je suis en 0.21bJe suis en 1.01
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
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