Code spécifique à l'appareil

Le système de récupération inclut plusieurs hooks pour insérer du code spécifique à l'appareil afin que les mises à jour OTA puissent également mettre à jour des parties de l'appareil autres que le système Android (par exemple, le processeur de bande de base ou radio).

Les sections et exemples suivants personnalisent l'appareil tardis produit par le fournisseur yoyodyne.

Carte des partitions

Depuis Android 2.3, la plate-forme est compatible avec les périphériques flash eMMC et le système de fichiers ext4 qui s'exécute sur ces périphériques. Il est également compatible avec les périphériques flash MTD (Memory Technology Device) et le système de fichiers yaffs2 des anciennes versions.

Le fichier de mappage de partition est spécifié par TARGET_RECOVERY_FSTAB. Ce fichier est utilisé à la fois par le binaire de récupération et par les outils de création de packages. Vous pouvez spécifier le nom du fichier de mappage dans TARGET_RECOVERY_FSTAB dans BoardConfig.mk.

Voici un exemple de fichier de mappage des partitions :

device/yoyodyne/tardis/recovery.fstab
# mount point       fstype  device       [device2]        [options (3.0+ only)]

/sdcard     vfat    /dev/block/mmcblk0p1 /dev/block/mmcblk0
/cache      yaffs2  cache
/misc       mtd misc
/boot       mtd boot
/recovery   emmc    /dev/block/platform/s3c-sdhci.0/by-name/recovery
/system     ext4    /dev/block/platform/s3c-sdhci.0/by-name/system length=-4096
/data       ext4    /dev/block/platform/s3c-sdhci.0/by-name/userdata

À l'exception de /sdcard, qui est facultatif, tous les points de montage de cet exemple doivent être définis (les appareils peuvent également ajouter des partitions supplémentaires). Cinq types de systèmes de fichiers sont acceptés :

yaffs2
Système de fichiers yaffs2 sur un périphérique flash MTD. "device" doit être le nom de la partition MTD et doit figurer dans /proc/mtd.
mtd
Partition MTD brute, utilisée pour les partitions amorçables telles que le démarrage et la récupération. MTD n'est pas réellement monté, mais le point de montage est utilisé comme clé pour localiser la partition. "device" doit être le nom de la partition MTD dans /proc/mtd.
ext4
Système de fichiers ext4 sur un périphérique flash eMMc. "device" doit correspondre au chemin d'accès du périphérique de bloc.
emmc
Périphérique de bloc eMMC brut, utilisé pour les partitions bootables telles que le démarrage et la récupération. Comme pour le type mtd, eMMc n'est jamais réellement monté, mais la chaîne du point de montage est utilisée pour localiser l'appareil dans le tableau.
vfat
Système de fichiers FAT sur un périphérique de blocs, généralement pour le stockage externe tel qu'une carte SD. "device" est le périphérique de bloc, tandis que "device2" est un deuxième périphérique de bloc que le système tente de monter si le montage du périphérique principal échoue (pour la compatibilité avec les cartes SD qui peuvent ou non être formatées avec une table de partition).

Toutes les partitions doivent être montées dans le répertoire racine (c'est-à-dire que la valeur du point de montage doit commencer par une barre oblique et ne pas en contenir d'autres). Cette restriction ne s'applique qu'au montage des systèmes de fichiers en mode Recovery. Le système principal est libre de les monter n'importe où. Les répertoires /boot, /recovery et /misc doivent être des types bruts (mtd ou emmc), tandis que les répertoires /system, /data, /cache et /sdcard (le cas échéant) doivent être des types de système de fichiers (yaffs2, ext4 ou vfat).

À partir d'Android 3.0, le fichier recovery.fstab comporte un champ facultatif supplémentaire, options. Actuellement, la seule option définie est length , qui vous permet de spécifier explicitement la longueur de la partition. Cette longueur est utilisée lors du reformatage de la partition (par exemple, pour la partition userdata lors d'une opération d'effacement des données/de rétablissement de la configuration d'usine, ou pour la partition système lors de l'installation d'un package OTA complet). Si la valeur de longueur est négative, la taille à formater est obtenue en ajoutant la valeur de longueur à la taille réelle de la partition. Par exemple, si vous définissez "length=-16384", les 16 ko de la fin de la partition ne seront pas écrasés lorsque cette partition sera reformatée. Cela permet de prendre en charge des fonctionnalités telles que le chiffrement de la partition userdata (où les métadonnées de chiffrement sont stockées à la fin de la partition et ne doivent pas être écrasées).

Remarque : Les champs device2 et options sont facultatifs, ce qui crée une ambiguïté lors de l'analyse. Si l'entrée du quatrième champ de la ligne commence par un caractère "/", elle est considérée comme une entrée device2. Si l'entrée ne commence pas par un caractère "/", elle est considérée comme un champ options.

Animation de démarrage

Les fabricants d'appareils peuvent personnaliser l'animation affichée lors du démarrage d'un appareil Android. Pour ce faire, créez un fichier .zip organisé et situé conformément aux spécifications du format bootanimation.

Pour les appareils Android Things, vous pouvez importer le fichier compressé dans la console Android Things afin que les images soient incluses dans le produit sélectionné.

Remarque : Ces images doivent respecter les consignes relatives à la marque Android. Pour connaître les consignes relatives à la marque, consultez la section Android du Partner Marketing Hub.

Interface utilisateur de récupération

Pour prendre en charge les appareils avec différents matériels disponibles (boutons physiques, LED, écrans, etc.), Vous pouvez personnaliser l'interface de récupération pour afficher l'état et accéder aux fonctionnalités cachées à commande manuelle pour chaque appareil.

Votre objectif est de créer une petite bibliothèque statique avec quelques objets C++ pour fournir la fonctionnalité spécifique à l'appareil. Le fichier bootable/recovery/default_device.cpp est utilisé par défaut et constitue un bon point de départ à copier lorsque vous écrivez une version de ce fichier pour votre appareil.

Remarque : Le message Aucune commande peut s'afficher. Pour activer ou désactiver le texte, appuyez de manière prolongée sur le bouton Marche/Arrêt tout en appuyant sur le bouton Volume+. Si votre appareil ne possède pas les deux boutons, appuyez de manière prolongée sur n'importe quel bouton pour activer ou désactiver le texte.

device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

Fonctions d'en-tête et d'élément

La classe Device nécessite des fonctions pour renvoyer les en-têtes et les éléments qui apparaissent dans le menu de récupération masqué. Les en-têtes décrivent comment utiliser le menu (c'est-à-dire les commandes permettant de modifier ou de sélectionner l'élément en surbrillance).

static const char* HEADERS[] = { "Volume up/down to move highlight;",
                                 "power button to select.",
                                 "",
                                 NULL };

static const char* ITEMS[] =  {"reboot system now",
                               "apply update from ADB",
                               "wipe data/factory reset",
                               "wipe cache partition",
                               NULL };

Remarque : Les lignes longues sont tronquées (et non renvoyées à la ligne). Tenez donc compte de la largeur de l'écran de votre appareil.

Personnaliser CheckKey

Ensuite, définissez l'implémentation RecoveryUI de votre appareil. Cet exemple suppose que l'appareil tardis dispose d'un écran. Vous pouvez donc hériter de l'implémentation ScreenRecoveryUI intégrée (consultez les instructions pour les appareils sans écran). La seule fonction à personnaliser à partir de ScreenRecoveryUI est CheckKey(), qui gère la gestion asynchrone initiale des touches :

class TardisUI : public ScreenRecoveryUI {
  public:
    virtual KeyAction CheckKey(int key) {
        if (key == KEY_HOME) {
            return TOGGLE;
        }
        return ENQUEUE;
    }
};

Constantes KEY

Les constantes KEY_* sont définies dans linux/input.h. CheckKey() est appelé quoi qu'il se passe dans le reste de la récupération : lorsque le menu est désactivé, lorsqu'il est activé, lors de l'installation du package, lors de l'effacement des données utilisateur, etc. Il peut renvoyer l'une des quatre constantes suivantes :

  • TOGGLE. Activer ou désactiver l'affichage du menu et/ou du journal de texte
  • REDÉMARRER Redémarrer immédiatement l'appareil
  • IGNORE. Ignorer cette pression sur une touche
  • ENQUEUE. Mettre en file d'attente cette pression sur une touche pour qu'elle soit consommée de manière synchrone (c'est-à-dire par le système de menu de récupération si l'écran est activé)

CheckKey() est appelé chaque fois qu'un événement "touche enfoncée" est suivi d'un événement "touche relâchée" pour la même touche. (La séquence d'événements A-down B-down B-up A-up n'entraîne que l'appel de CheckKey(B).) CheckKey() peut appeler IsKeyPressed() pour savoir si d'autres touches sont maintenues enfoncées. (Dans la séquence d'événements clés ci-dessus, si CheckKey(B) avait appelé IsKeyPressed(A), il aurait renvoyé "true".)

CheckKey() peut conserver l'état dans sa classe, ce qui peut être utile pour détecter des séquences de touches. Cet exemple montre une configuration légèrement plus complexe : l'écran est activé en maintenant le bouton Marche/Arrêt enfoncé et en appuyant sur le bouton Volume +, et l'appareil peut être redémarré immédiatement en appuyant cinq fois de suite sur le bouton Marche/Arrêt (sans appuyer sur d'autres touches entre-temps) :

class TardisUI : public ScreenRecoveryUI {
  private:
    int consecutive_power_keys;

  public:
    TardisUI() : consecutive_power_keys(0) {}

    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 5) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }
};

ScreenRecoveryUI

Lorsque vous utilisez vos propres images (icône d'erreur, animation d'installation, barres de progression) avec ScreenRecoveryUI, vous pouvez définir la variable animation_fps pour contrôler la vitesse des animations en images par seconde (FPS).

Remarque : Le script interlace-frames.py actuel vous permet de stocker les informations animation_fps dans l'image elle-même. Dans les versions antérieures d'Android, il était nécessaire de définir animation_fps vous-même.

Pour définir la variable animation_fps, remplacez la fonction ScreenRecoveryUI::Init() dans votre sous-classe. Définissez la valeur, puis appelez la fonction parent Init() pour terminer l'initialisation. La valeur par défaut (20 FPS) correspond aux images de récupération par défaut. Lorsque vous utilisez ces images, vous n'avez pas besoin de fournir de fonction Init(). Pour en savoir plus sur les images, consultez Images de l'UI de récupération.

Classe d'appareil

Une fois que vous avez une implémentation RecoveryUI, définissez votre classe d'appareil (sous-classe de la classe Device intégrée). Elle doit créer une instance unique de votre classe d'UI et la renvoyer à partir de la fonction GetUI() :

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

StartRecovery

La méthode StartRecovery() est appelée au début de la récupération, après l'initialisation de l'UI et l'analyse des arguments, mais avant toute action. L'implémentation par défaut ne fait rien. Vous n'avez donc pas besoin de la fournir dans votre sous-classe si vous n'avez rien à faire :

   void StartRecovery() {
       // ... do something tardis-specific here, if needed ....
    }

Fournir et gérer le menu de récupération

Le système appelle deux méthodes pour obtenir la liste des lignes d'en-tête et la liste des éléments. Dans cette implémentation, il renvoie les tableaux statiques définis en haut du fichier :

const char* const* GetMenuHeaders() { return HEADERS; }
const char* const* GetMenuItems() { return ITEMS; }

HandleMenuKey

Ensuite, fournissez une fonction HandleMenuKey(), qui prend une frappe au clavier et la visibilité actuelle du menu, et décide de l'action à entreprendre :

   int HandleMenuKey(int key, int visible) {
        if (visible) {
            switch (key) {
              case KEY_VOLUMEDOWN: return kHighlightDown;
              case KEY_VOLUMEUP:   return kHighlightUp;
              case KEY_POWER:      return kInvokeItem;
            }
        }
        return kNoAction;
    }

La méthode prend un code de touche (qui a été précédemment traité et mis en file d'attente par la méthode CheckKey() de l'objet UI) et l'état actuel de la visibilité du menu/journal de texte. La valeur renvoyée est un nombre entier. Si la valeur est égale ou supérieure à 0, elle est considérée comme la position d'un élément de menu, qui est appelé immédiatement (voir la méthode InvokeMenuItem() ci-dessous). Sinon, il peut s'agir de l'une des constantes prédéfinies suivantes :

  • kHighlightUp. Déplacer la mise en surbrillance du menu vers l'élément précédent
  • kHighlightDown. Déplacer la mise en surbrillance du menu vers l'élément suivant
  • kInvokeItem. Appeler l'élément actuellement en surbrillance
  • kNoAction. Ne rien faire avec cette frappe

Comme l'implique l'argument visible, HandleMenuKey() est appelé même si le menu n'est pas visible. Contrairement à CheckKey(), elle n'est pas appelée lorsque la récupération effectue une action telle que l'effacement des données ou l'installation d'un package. Elle n'est appelée que lorsque la récupération est inactive et attend une entrée.

Mécanismes de trackball

Si votre appareil dispose d'un mécanisme de saisie de type trackball (qui génère des événements de saisie de type EV_REL et de code REL_Y), la récupération synthétise les pressions sur les touches KEY_UP et KEY_DOWN chaque fois que le mécanisme de saisie de type trackball signale un mouvement sur l'axe Y. Il vous suffit de mapper les événements KEY_UP et KEY_DOWN sur les actions de menu. Ce mappage ne se produit pas pour CheckKey(). Vous ne pouvez donc pas utiliser les mouvements du trackball comme déclencheurs pour redémarrer ou activer/désactiver l'écran.

Touches de modification

Pour vérifier si des touches sont maintenues enfoncées en tant que modificateurs, appelez la méthode IsKeyPressed() de votre propre objet d'UI. Par exemple, sur certains appareils, appuyer sur Alt+W en mode Recovery déclenchait l'effacement des données, que le menu soit visible ou non. Vous pouvez implémenter comme suit :

   int HandleMenuKey(int key, int visible) {
        if (ui->IsKeyPressed(KEY_LEFTALT) && key == KEY_W) {
            return 2;  // position of the "wipe data" item in the menu
        }
        ...
    }

Remarque : Si la valeur visible est définie sur "false", il n'est pas logique de renvoyer les valeurs spéciales qui manipulent le menu (déplacer la sélection, appeler l'élément sélectionné), car l'utilisateur ne peut pas voir la sélection. Toutefois, vous pouvez renvoyer les valeurs si vous le souhaitez.

InvokeMenuItem

Ensuite, fournissez une méthode InvokeMenuItem() qui mappe les positions entières dans le tableau d'éléments renvoyés par GetMenuItems() aux actions. Pour le tableau d'éléments dans l'exemple tardis, utilisez :

   BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          case 1: return APPLY_ADB_SIDELOAD;
          case 2: return WIPE_DATA;
          case 3: return WIPE_CACHE;
          default: return NO_ACTION;
        }
    }

Cette méthode peut renvoyer n'importe quel membre de l'énumération BuiltinAction pour indiquer au système d'effectuer cette action (ou le membre NO_ACTION si vous ne voulez pas que le système fasse quoi que ce soit). C'est l'endroit où fournir des fonctionnalités de récupération supplémentaires au-delà de celles du système : ajoutez un élément pour cela dans votre menu, exécutez-le ici lorsque cet élément de menu est appelé et renvoyez NO_ACTION pour que le système ne fasse rien d'autre.

BuiltinAction contient les valeurs suivantes :

  • NO_ACTION. Ne rien faire.
  • REDÉMARRER Quittez le mode Recovery et redémarrez l'appareil normalement.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Installez un package de mise à jour à partir de différents emplacements. Pour en savoir plus, consultez Installation de fichiers APK.
  • WIPE_CACHE. Reformatez uniquement la partition du cache. Aucune confirmation n'est requise, car il s'agit d'une action relativement inoffensive.
  • WIPE_DATA. Reformattez les partitions userdata et cache, également appelées "rétablissement de la configuration d'usine". L'utilisateur est invité à confirmer cette action avant de continuer.

La dernière méthode, WipeData(), est facultative et est appelée chaque fois qu'une opération d'effacement des données est lancée (soit à partir de la récupération via le menu, soit lorsque l'utilisateur a choisi de rétablir la configuration d'usine à partir du système principal). Cette méthode est appelée avant l'effacement des partitions de données utilisateur et de cache. Si votre appareil stocke des données utilisateur ailleurs que dans ces deux partitions, vous devez les effacer ici. Vous devez renvoyer 0 pour indiquer le succès et une autre valeur en cas d'échec, bien que la valeur renvoyée soit actuellement ignorée. Les partitions de données utilisateur et de cache sont effacées, que vous renvoyiez une réussite ou un échec.

   int WipeData() {
       // ... do something tardis-specific here, if needed ....
       return 0;
    }

Marque de l'appareil

Enfin, incluez du code passe-partout à la fin du fichier recovery_ui.cpp pour la fonction make_device() qui crée et renvoie une instance de votre classe Device :

class TardisDevice : public Device {
   // ... all the above methods ...
};

Device* make_device() {
    return new TardisDevice();
}

Une fois le fichier recovery_ui.cpp terminé, compilez-le et associez-le à la récupération sur votre appareil. Dans Android.mk, créez une bibliothèque statique qui ne contient que ce fichier C++ :

device/yoyodyne/tardis/recovery/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp

# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_tardis

include $(BUILD_STATIC_LIBRARY)

Ensuite, dans la configuration de la carte pour cet appareil, spécifiez votre bibliothèque statique comme valeur de TARGET_RECOVERY_UI_LIB.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

Images de l'UI de récupération

L'interface utilisateur de récupération se compose d'images. Dans l'idéal, les utilisateurs n'interagissent jamais avec l'UI : lors d'une mise à jour normale, le téléphone démarre en mode récupération, remplit la barre de progression de l'installation et redémarre dans le nouveau système sans intervention de l'utilisateur. En cas de problème de mise à jour du système, la seule action que l'utilisateur peut effectuer est d'appeler le service client.

Une interface uniquement basée sur des images n'a pas besoin d'être localisée. Toutefois, à partir d'Android 5.0, la mise à jour peut afficher une chaîne de texte (par exemple, "Installation de la mise à jour du système…") en plus de l'image. Pour en savoir plus, consultez Texte de récupération localisé.

Android 5.0 ou version ultérieure

L'UI de récupération d'Android 5.0 et versions ultérieures utilise deux images principales : l'image error et l'animation installing.

Image affichée en cas d&#39;erreur OTA

Figure 1 : icon_error.png

Image affichée lors de l&#39;installation OTA

Figure 2 : icon_installing.png

L'animation d'installation est représentée par une seule image PNG avec différentes images de l'animation entrelacées par ligne (c'est pourquoi la figure 2 semble écrasée). Par exemple, pour une animation de sept images de 200 x 200, créez une seule image de 200 x 1 400, où la première image correspond aux lignes 0, 7, 14, 21, etc., la deuxième image aux lignes 1, 8, 15, 22, etc. L'image combinée inclut un bloc de texte qui indique le nombre d'images de l'animation et le nombre d'images par seconde (FPS). L'outil bootable/recovery/interlace-frames.py prend un ensemble de frames d'entrée et les combine dans l'image composite nécessaire à la récupération.

Les images par défaut sont disponibles dans différentes densités et se trouvent dans bootable/recovery/res-$DENSITY/images (par exemple, bootable/recovery/res-hdpi/images). Pour utiliser une image statique lors de l'installation, il vous suffit de fournir l'image icon_installing.png et de définir le nombre de frames dans l'animation sur 0 (l'icône d'erreur n'est pas animée, elle est toujours une image statique).

Android 4.x et versions antérieures

L'interface utilisateur de récupération d'Android 4.x et versions antérieures utilise l'image error (erreur, illustrée ci-dessus) et l'animation installing (installation) ainsi que plusieurs images de superposition :

Image affichée lors de l&#39;installation OTA

Figure 3 : icon_installing.png

image affichée en tant que première superposition

Figure 4. icon-installing_overlay01.png

image affichée comme septième
calque

Figure 5 : icon_installing_overlay07.png

Lors de l'installation, l'affichage à l'écran est construit en dessinant l'image icon_installing.png, puis en dessinant l'un des cadres de superposition par-dessus, avec le décalage approprié. Ici, un cadre rouge est superposé pour mettre en évidence l'emplacement de la superposition sur l'image de base :

image composite de l&#39;installation et de la première superposition

Figure 6. Installation du frame d'animation 1 (icon_installing.png + icon_installing_overlay01.png)

image composite de l&#39;installation et de la septième superposition

Figure 7. Installation du frame d'animation 7 (icon_installing.png + icon_installing_overlay07.png)

Les images suivantes sont affichées en dessinant uniquement la prochaine image superposée sur ce qui est déjà là. L'image de base n'est pas redessinée.

Le nombre de frames dans l'animation, la vitesse souhaitée et les décalages X et Y de la superposition par rapport à la base sont définis par les variables membres de la classe ScreenRecoveryUI. Lorsque vous utilisez des images personnalisées au lieu des images par défaut, remplacez la méthode Init() dans votre sous-classe pour modifier ces valeurs pour vos images personnalisées (pour en savoir plus, consultez ScreenRecoveryUI). Le script bootable/recovery/make-overlay.py peut vous aider à convertir un ensemble de frames d'image au format "image de base + images de superposition" requis pour la récupération, y compris le calcul des décalages nécessaires.

Les images par défaut se trouvent dans bootable/recovery/res/images. Pour utiliser une image statique lors de l'installation, il vous suffit de fournir l'image icon_installing.png et de définir le nombre de frames dans l'animation sur 0 (l'icône d'erreur n'est pas animée, elle est toujours une image statique).

Texte de récupération localisé

Android 5.x affiche une chaîne de texte (par exemple, "Installation de la mise à jour du système…") ainsi que l'image. Lorsque le système principal démarre en mode Recovery, il transmet les paramètres régionaux actuels de l'utilisateur en tant qu'option de ligne de commande à Recovery. Pour chaque message à afficher, la récupération inclut une deuxième image composite avec des chaînes de texte prérendues pour ce message dans chaque langue.

Exemple d'image de chaînes de texte de récupération :

image du texte de récupération

Figure 8. Texte localisé pour les messages de récupération

Le texte de récupération peut afficher les messages suivants :

  • Installation de la mise à jour du système…
  • Erreur !
  • Suppression... (lorsque vous effacez les données ou rétablissez la configuration d'usine)
  • Aucune commande (lorsqu'un utilisateur démarre manuellement en mode Recovery)

L'application Android dans bootable/recovery/tools/recovery_l10n/ affiche les traductions d'un message et crée l'image composite. Pour en savoir plus sur l'utilisation de cette application, consultez les commentaires dans bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java.

Lorsqu'un utilisateur démarre manuellement en mode Recovery, il est possible que les paramètres régionaux ne soient pas disponibles et qu'aucun texte ne s'affiche. Ne rendez pas les messages critiques pour le processus de récupération.

Remarque : L'interface masquée qui affiche les messages du journal et permet à l'utilisateur de sélectionner des actions dans le menu n'est disponible qu'en anglais.

Barres de progression

Les barres de progression peuvent s'afficher sous l'image (ou l'animation) principale. La barre de progression est créée en combinant deux images d'entrée, qui doivent avoir la même taille :

Barre de progression vide

Figure 9 : progress_empty.png

Barre de progression complète

Figure 10 : progress_fill.png

L'extrémité gauche de l'image fill (remplie) est affichée à côté de l'extrémité droite de l'image empty (vide) pour former la barre de progression. La position de la limite entre les deux images est modifiée pour indiquer la progression. Par exemple, avec les paires d'images d'entrée ci-dessus, l'affichage est le suivant :

Barre de progression à 1 %

Figure 11 : Barre de progression à 1 %>

Barre de progression à 10 %

Figure 12. Barre de progression à 10 %

barre de progression à 50 %

Figure 13. Barre de progression à 50 %

Vous pouvez fournir des versions spécifiques à l'appareil de ces images en les plaçant dans (dans cet exemple) device/yoyodyne/tardis/recovery/res/images. Les noms de fichiers doivent correspondre à ceux listés ci-dessus. Lorsqu'un fichier est trouvé dans ce répertoire, le système de compilation l'utilise de préférence à l'image par défaut correspondante. Seuls les PNG au format RVB ou RVBA avec une profondeur de couleur de 8 bits sont acceptés.

Remarque : Dans Android 5.x, si le paramètre régional est connu de la récupération et qu'il s'agit d'une langue qui se lit de droite à gauche (arabe, hébreu, etc.), la barre de progression se remplit de droite à gauche.

Appareils sans écran

Tous les appareils Android ne sont pas équipés d'un écran. Si votre appareil est un appareil sans écran ou possède une interface audio uniquement, vous devrez peut-être personnaliser davantage l'UI de récupération. Au lieu de créer une sous-classe de ScreenRecoveryUI, créez directement une sous-classe de sa classe parente RecoveryUI.

RecoveryUI dispose de méthodes permettant de gérer les opérations d'interface utilisateur de niveau inférieur, telles que "basculer l'affichage", "mettre à jour la barre de progression", "afficher le menu", "modifier la sélection du menu", etc. Vous pouvez les remplacer pour fournir une interface appropriée à votre appareil. Votre appareil peut être équipé de voyants LED dont vous pouvez utiliser différentes couleurs ou différents schémas de clignotement pour indiquer l'état, ou vous pouvez lire de l'audio. (Peut-être ne souhaitez-vous pas du tout prendre en charge un menu ou le mode "affichage de texte" ; vous pouvez empêcher l'accès à ces éléments avec des implémentations CheckKey() et HandleMenuKey() qui ne basculent jamais l'affichage sur "on" ni ne sélectionnent un élément de menu. Dans ce cas, de nombreuses méthodes RecoveryUI que vous devez fournir peuvent simplement être des stubs vides.)

Consultez bootable/recovery/ui.h pour la déclaration de RecoveryUI afin de voir les méthodes que vous devez prendre en charge. RecoveryUI est abstrait (certaines méthodes sont virtuelles pures et doivent être fournies par les sous-classes), mais il contient le code permettant de traiter les entrées de clé. Vous pouvez également remplacer cette valeur si votre appareil ne possède pas de clés ou si vous souhaitez les traiter différemment.

Mise à jour

Vous pouvez utiliser du code spécifique à l'appareil lors de l'installation du package de mise à jour en fournissant vos propres fonctions d'extension qui peuvent être appelées depuis votre script de mise à jour. Voici un exemple de fonction pour l'appareil tardis :

device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h>
#include <string.h>

#include "edify/expr.h"

Chaque fonction d'extension a la même signature. Les arguments sont le nom par lequel la fonction a été appelée, un cookie State*, le nombre d'arguments entrants et un tableau de pointeurs Expr* représentant les arguments. La valeur renvoyée est un Value* nouvellement alloué.

Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 2) {
        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
    }

Vos arguments n'ont pas été évalués au moment où votre fonction est appelée. La logique de votre fonction détermine lesquels d'entre eux sont évalués et combien de fois. Vous pouvez donc utiliser des fonctions d'extension pour implémenter vos propres structures de contrôle. Call Evaluate() pour évaluer un argument Expr* et renvoyer une valeur Value*. Si Evaluate() renvoie NULL, vous devez libérer toutes les ressources que vous détenez et renvoyer immédiatement NULL (cela propage les abandons dans la pile Edify). Sinon, vous devenez propriétaire de la valeur renvoyée et êtes responsable de l'appel éventuel de FreeValue() sur celle-ci.

Supposons que la fonction ait besoin de deux arguments : une clé de type chaîne et une image de type blob. Vous pouvez lire des arguments comme celui-ci :

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

La vérification de la valeur NULL et la libération des arguments précédemment évalués peuvent devenir fastidieuses pour plusieurs arguments. La fonction ReadValueArgs() peut vous faciliter la tâche. Au lieu du code ci-dessus, vous auriez pu écrire ceci :

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs() n'effectue pas de vérification du type, vous devez donc le faire ici. Il est plus pratique de le faire avec une seule instruction if au prix d'un message d'erreur un peu moins spécifique en cas d'échec. Toutefois, ReadValueArgs() gère l'évaluation de chaque argument et libère tous les arguments précédemment évalués (tout en définissant un message d'erreur utile) si l'une des évaluations échoue. Vous pouvez utiliser une fonction pratique ReadValueVarArgs() pour évaluer un nombre variable d'arguments (elle renvoie un tableau de Value*).

Après avoir évalué les arguments, effectuez le travail de la fonction :

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

La valeur renvoyée doit être un objet Value*. La propriété de cet objet sera transmise à l'appelant. L'appelant devient propriétaire de toutes les données pointées par ce Value*, en particulier le membre de données.

Dans ce cas, vous souhaitez renvoyer une valeur "true" ou "false" pour indiquer la réussite. N'oubliez pas la convention selon laquelle la chaîne vide est false et toutes les autres chaînes sont true. Vous devez allouer un objet Value avec une copie malloc de la chaîne constante à renvoyer, car l'appelant free() les deux. N'oubliez pas d'appeler FreeValue() sur les objets que vous avez obtenus en évaluant vos arguments !

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

La fonction utilitaire StringValue() encapsule une chaîne dans un nouvel objet Value. Utilisez le code ci-dessus de manière plus concise :

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

Pour accrocher des fonctions à l'interpréteur Edify, fournissez la fonction Register_foofoo est le nom de la bibliothèque statique contenant ce code. Appelez RegisterFunction() pour enregistrer chaque fonction d'extension. Par convention, nommez les fonctions spécifiques à l'appareil device.whatever pour éviter les conflits avec les futures fonctions intégrées ajoutées.

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

Vous pouvez maintenant configurer le fichier makefile pour compiler une bibliothèque statique avec votre code. (Il s'agit du même fichier makefile que celui utilisé pour personnaliser l'UI de récupération dans la section précédente. Il est possible que votre appareil comporte les deux bibliothèques statiques définies ici.)

device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

Le nom de la bibliothèque statique doit correspondre au nom de la fonction Register_libname qu'elle contient.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Enfin, configurez la compilation de la récupération pour extraire votre bibliothèque. Ajoutez votre bibliothèque à TARGET_RECOVERY_UPDATER_LIBS (qui peut contenir plusieurs bibliothèques ; elles sont toutes enregistrées). Si votre code dépend d'autres bibliothèques statiques qui ne sont pas elles-mêmes des extensions edify (c'est-à-dire ils n'ont pas de fonction Register_libname), vous pouvez les lister dans TARGET_RECOVERY_UPDATER_EXTRA_LIBS pour les associer au programme de mise à jour sans appeler leur fonction d'enregistrement (inexistante). Par exemple, si votre code spécifique à l'appareil souhaite utiliser zlib pour décompresser des données, vous devez inclure libz ici.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

Les scripts de mise à jour de votre package OTA peuvent désormais appeler votre fonction comme n'importe quelle autre. Pour reprogrammer votre appareil tardis, le script de mise à jour peut contenir : tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Cela utilise la version à un seul argument de la fonction intégrée package_extract_file(), qui renvoie le contenu d'un fichier extrait du package de mise à jour sous forme de blob pour produire le deuxième argument de la nouvelle fonction d'extension.

Génération de packages OTA

Le dernier composant consiste à informer les outils de génération de packages OTA de vos données spécifiques à l'appareil et à émettre des scripts de mise à jour qui incluent des appels à vos fonctions d'extension.

Tout d'abord, informez le système de compilation d'un blob de données spécifique à un appareil. En supposant que votre fichier de données se trouve dans device/yoyodyne/tardis/tardis.dat, déclarez les éléments suivants dans le fichier AndroidBoard.mk de votre appareil :

device/yoyodyne/tardis/AndroidBoard.mk
  [...]

$(call add-radio-file,tardis.dat)

Vous pouvez également le placer dans un fichier Android.mk, mais il doit alors être protégé par une vérification de l'appareil, car tous les fichiers Android.mk de l'arborescence sont chargés, quel que soit l'appareil en cours de compilation. (Si votre arbre inclut plusieurs appareils, vous ne souhaitez ajouter le fichier tardis.dat que lors de la compilation de l'appareil tardis.)

device/yoyodyne/tardis/Android.mk
  [...]

# an alternative to specifying it in AndroidBoard.mk
ifeq (($TARGET_DEVICE),tardis)
  $(call add-radio-file,tardis.dat)
endif

Pour des raisons historiques, ces fichiers sont appelés "fichiers radio". Ils peuvent n'avoir aucun rapport avec la radio de l'appareil (le cas échéant). Il s'agit simplement de blobs de données opaques que le système de compilation copie dans le fichier .zip target-files utilisé par les outils de génération OTA. Lorsque vous effectuez une compilation, tardis.dat est stocké dans target-files.zip sous le nom RADIO/tardis.dat. Vous pouvez appeler add-radio-file plusieurs fois pour ajouter autant de fichiers que vous le souhaitez.

Module Python

Pour étendre les outils de publication, écrivez un module Python (qui doit être nommé releasetools.py) que les outils peuvent appeler s'il est présent. Exemple :

device/yoyodyne/tardis/releasetools.py
import common

def FullOTA_InstallEnd(info):
  # copy the data into the package.
  tardis_dat = info.input_zip.read("RADIO/tardis.dat")
  common.ZipWriteStr(info.output_zip, "tardis.dat", tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Une fonction distincte gère la génération d'un package OTA incrémental. Pour cet exemple, supposons que vous deviez reprogrammer le tardis uniquement lorsque le fichier tardis.dat a été modifié entre deux versions.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Fonctions du module

Vous pouvez fournir les fonctions suivantes dans le module (n'implémentez que celles dont vous avez besoin).

FullOTA_Assertions()
Appelé près du début de la génération d'une OTA complète. C'est l'endroit idéal pour émettre des assertions sur l'état actuel de l'appareil. N'émettez pas de commandes de script qui modifient l'appareil.
FullOTA_InstallBegin()
Appelée une fois que toutes les assertions sur l'état de l'appareil ont été validées, mais avant que des modifications aient été apportées. Vous pouvez émettre des commandes pour des mises à jour spécifiques à l'appareil qui doivent s'exécuter avant toute autre modification sur l'appareil.
FullOTA_InstallEnd()
Appelé à la fin de la génération du script, après l'émission des commandes de script pour mettre à jour les partitions de démarrage et système. Vous pouvez également émettre des commandes supplémentaires pour les mises à jour spécifiques à l'appareil.
IncrementalOTA_Assertions()
Semblable à FullOTA_Assertions(), mais appelé lors de la génération d'un package de mise à jour incrémentielle.
IncrementalOTA_VerifyBegin()
Appelée une fois que toutes les assertions sur l'état de l'appareil ont réussi, mais avant que des modifications aient été apportées. Vous pouvez émettre des commandes pour des mises à jour spécifiques à l'appareil qui doivent s'exécuter avant toute autre modification sur l'appareil.
IncrementalOTA_VerifyEnd()
Appelé à la fin de la phase de validation, lorsque le script a fini de confirmer que les fichiers qu'il va toucher ont le contenu de départ attendu. À ce stade, rien n'a été modifié sur l'appareil. Vous pouvez également émettre du code pour des vérifications supplémentaires spécifiques à l'appareil.
IncrementalOTA_InstallBegin()
Appelé après que les fichiers à corriger ont été vérifiés comme ayant l'état avant attendu, mais avant que des modifications aient été apportées. Vous pouvez émettre des commandes pour des mises à jour spécifiques à l'appareil qui doivent s'exécuter avant toute autre modification sur l'appareil.
IncrementalOTA_InstallEnd()
Comme son homologue de package OTA complet, cette fonction est appelée à la fin de la génération du script, après l'émission des commandes de script permettant de mettre à jour les partitions de démarrage et système. Vous pouvez également émettre des commandes supplémentaires pour les mises à jour spécifiques à l'appareil.

Remarque : Si l'appareil n'est plus alimenté, l'installation OTA peut redémarrer depuis le début. Soyez prêt à gérer les appareils sur lesquels ces commandes ont déjà été exécutées, entièrement ou partiellement.

Transmettre des fonctions aux objets d'informations

Transmettez les fonctions à un seul objet d'informations contenant divers éléments utiles :

  • info.input_zip. (Mises à jour OTA complètes uniquement) Objet zipfile.ZipFile pour le fichier .zip target-files d'entrée.
  • info.source_zip. (OTA incrémentiels uniquement) Objet zipfile.ZipFile pour le fichier .zip target-files source (compilation déjà sur l'appareil lors de l'installation du package incrémentiel).
  • info.target_zip. (OTA incrémentiels uniquement) Objet zipfile.ZipFile pour le fichier .zip target-files cible (compilation que le package incrémental place sur l'appareil).
  • info.output_zip. Le package est en cours de création. Un objet zipfile.ZipFile est ouvert pour l'écriture. Utilisez common.ZipWriteStr(info.output_zip, filename, data) pour ajouter un fichier au package.
  • info.script. Objet de script auquel vous pouvez ajouter des commandes. Appelez info.script.AppendExtra(script_text) pour afficher du texte dans le script. Assurez-vous que le texte de sortie se termine par un point-virgule afin qu'il ne rencontre pas les commandes émises par la suite.

Pour en savoir plus sur l'objet info, consultez la documentation de la Python Software Foundation sur les archives ZIP.

Spécifier l'emplacement du module

Spécifiez l'emplacement du script releasetools.py de votre appareil dans votre fichier BoardConfig.mk :

device/yoyodyne/tardis/BoardConfig.mk
 [...]

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Si TARGET_RELEASETOOLS_EXTENSIONS n'est pas défini, la valeur par défaut est le répertoire $(TARGET_DEVICE_DIR)/../common (device/yoyodyne/common dans cet exemple). Il est préférable de définir explicitement l'emplacement du script releasetools.py. Lors de la création de l'appareil tardis, le script releasetools.py est inclus dans le fichier .zip target-files (META/releasetools.py ).

Lorsque vous exécutez les outils de publication (img_from_target_files ou ota_from_target_files), le script releasetools.py dans le fichier .zip target-files, s'il est présent, est préféré à celui de l'arborescence source Android. Vous pouvez également spécifier explicitement le chemin d'accès aux extensions spécifiques à l'appareil avec l'option -s (ou --device_specific), qui est prioritaire. Cela vous permet de corriger les erreurs et d'apporter des modifications dans les extensions releasetools, puis de les appliquer aux anciens fichiers cibles.

Désormais, lorsque vous exécutez ota_from_target_files, il récupère automatiquement le module spécifique à l'appareil à partir du fichier .zip target_files et l'utilise lors de la génération des packages OTA :

./build/make/tools/releasetools/ota_from_target_files \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

Vous pouvez également spécifier des extensions spécifiques à l'appareil lorsque vous exécutez ota_from_target_files.

./build/make/tools/releasetools/ota_from_target_files \
    -s device/yoyodyne/tardis \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

Remarque : Pour obtenir la liste complète des options, consultez les commentaires ota_from_target_files dans build/make/tools/releasetools/ota_from_target_files.

Mécanisme de téléchargement indépendant

La récupération dispose d'un mécanisme de chargement latéral pour installer manuellement un package de mise à jour sans le télécharger par voie hertzienne via le système principal. Le téléchargement indépendant est utile pour le débogage ou pour apporter des modifications sur des appareils sur lesquels le système principal ne peut pas démarrer.

Historiquement, le transfert de fichiers s'effectuait en chargeant des packages depuis la carte SD de l'appareil. Dans le cas d'un appareil qui ne démarre pas, le package peut être placé sur la carte SD à l'aide d'un autre ordinateur, puis la carte SD peut être insérée dans l'appareil. Pour prendre en charge les appareils Android sans stockage externe amovible, la récupération accepte deux mécanismes supplémentaires pour le chargement indépendant : le chargement de packages à partir de la partition de cache et le chargement via USB à l'aide d'adb.

Pour appeler chaque mécanisme de transfert, la méthode Device::InvokeMenuItem() de votre appareil peut renvoyer les valeurs BuiltinAction suivantes :

  • APPLY_EXT Téléchargez un package de mise à jour depuis un stockage externe (répertoire /sdcard). Votre fichier recovery.fstab doit définir le point de montage /sdcard . Cette méthode n'est pas utilisable sur les appareils qui émulent une carte SD avec un lien symbolique vers /data (ou un mécanisme similaire). /data n'est généralement pas disponible pour la récupération, car il peut être chiffré. L'UI de récupération affiche un menu de fichiers .zip dans /sdcard et permet à l'utilisateur d'en sélectionner un.
  • APPLY_CACHE Semblable au chargement d'un package à partir de /sdcard, sauf que le répertoire /cache (qui est toujours disponible pour la récupération) est utilisé à la place. Dans le système normal, /cache n'est accessible en écriture qu'aux utilisateurs privilégiés. De plus, si l'appareil n'est pas bootable, le répertoire /cache n'est pas accessible en écriture du tout (ce qui limite l'utilité de ce mécanisme).
  • APPLY_ADB_SIDELOAD Permet à l'utilisateur d'envoyer un package à l'appareil via un câble USB et l'outil de développement adb. Lorsque ce mécanisme est invoqué, la récupération démarre sa propre mini-version du daemon adbd pour permettre à adb sur un ordinateur hôte connecté de communiquer avec lui. Cette version mini ne prend en charge qu'une seule commande : adb sideload filename. Le fichier nommé est envoyé de la machine hôte vers l'appareil, qui le valide et l'installe comme s'il se trouvait dans le stockage local.

Quelques mises en garde :

  • Seul le transport USB est pris en charge.
  • Si votre récupération exécute adbd normalement (généralement le cas pour les versions userdebug et eng), il sera arrêté lorsque l'appareil est en mode sideload adb et redémarré lorsque sideload adb aura fini de recevoir un package. En mode sideload adb, aucune commande adb autre que sideload ne fonctionne ( logcat, reboot, push, pull , shell, etc. échouent toutes).
  • Vous ne pouvez pas quitter le mode sideload adb sur l'appareil. Pour annuler, vous pouvez envoyer /dev/null (ou tout autre package non valide) en tant que package. L'appareil ne pourra alors pas le valider et arrêtera la procédure d'installation. La méthode CheckKey() de l'implémentation RecoveryUI continuera d'être appelée pour les frappes sur les touches. Vous pouvez donc fournir une séquence de touches qui redémarre l'appareil et fonctionne en mode sideload adb.