66
SOLUTION D’OTA Pierre-Olivier Dybman Edouard Marquez +PierreOlivierDybman (aka appy beard) +EdouardMarquez (aka g123k)

Solution d'OTA

  • Upload
    sidereo

  • View
    388

  • Download
    0

Embed Size (px)

Citation preview

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

3

QUI SOMMES NOUS ?

Bla bla sidereoMONTRÉAL

PARIS

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é

5

QUI SOMMES NOUS ?

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

37

Exécution de la mise à jour

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é

42

BACKEND

Imaginons :

Ca, c’est notre téléphone Android

43

BACKEND

Imaginons :

Il y en a plein

44

BACKEND

Imaginons :

Il y en a aussi plein d’autres types

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

47

BACKEND

48

BACKEND

Pour résumer JSON

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

50

BACKEND

Pour résumer

Quelques captures d’écran de l’outil

51

BACKEND

Des questions ?