Code spécifique à l'appareil

Le système de récupération comprend 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, la bande de base ou le processeur radio).

Les sections et exemples suivants personnalisent le périphérique tardis produit par le fournisseur Yoyodyne .

Carte de partition

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

Le fichier de mappe 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 carte dans TARGET_RECOVERY_FSTAB dans BoardConfig.mk.

Un exemple de fichier de mappe de partition pourrait ressembler à ceci :

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 dans cet exemple doivent être définis (les appareils peuvent également ajouter des partitions supplémentaires). Il existe cinq types de systèmes de fichiers pris en charge :

yaffs2
Un système de fichiers yaffs2 au sommet d'un périphérique flash MTD. "device" doit être le nom de la partition MTD et doit apparaître dans /proc/mtd .
mdt
Une 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 .
poste4
Un système de fichiers ext4 au sommet d'un périphérique flash eMMc. "device" doit être le chemin du périphérique bloc.
emmc
Un périphérique de bloc eMMc brut, utilisé pour les partitions amorçables telles que le démarrage et la récupération. Semblable au type mtd, eMMc n'est jamais réellement monté, mais la chaîne du point de montage est utilisée pour localiser le périphérique dans la table.
vfat
Un système de fichiers FAT au-dessus d'un périphérique bloc, généralement pour le stockage externe tel qu'une carte SD. Le périphérique est le périphérique bloc ; périphérique2 est un deuxième périphérique 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 comporter aucune autre barre oblique). Cette restriction s'applique uniquement au montage de systèmes de fichiers en cours de récupération ; 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 (si disponible) doivent être des types de système de fichiers (yaffs2, ext4 ou vgras).

À partir d'Android 3.0, le fichier recovery.fstab gagne 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 de données utilisateur lors d'une opération d'effacement des données/de réinitialisation 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 prise en ajoutant la valeur de longueur à la taille réelle de la partition. Par exemple, définir « length=-16384 » signifie que les 16 derniers Ko de cette partition ne seront pas écrasés lorsque cette partition sera reformatée. Cela prend en charge des fonctionnalités telles que le chiffrement de la partition de données utilisateur (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 dans le quatrième champ de la ligne commence par un caractère « / », elle est considérée comme une entrée de périphérique 2 ; si l'entrée ne commence pas par un caractère « / », elle est considérée comme un champ d'options .

Animation de démarrage

Les fabricants d'appareils ont la possibilité de personnaliser l'animation affichée lors du démarrage d'un appareil Android. Pour ce faire, construisez un fichier .zip organisé et localisé selon les spécifications au format bootanimation .

Pour les appareils Android Things , vous pouvez télécharger le fichier compressé dans la console Android Things pour inclure les images dans le produit sélectionné.

Remarque : Ces images doivent respecter les directives de la marque Android.

Interface utilisateur de récupération

Pour prendre en charge les appareils dotés de différents matériels disponibles (boutons physiques, voyants, écrans, etc.), vous pouvez personnaliser l'interface de récupération pour afficher l'état et accéder aux fonctionnalités cachées commandées manuellement pour chaque appareil.

Votre objectif est de créer une petite bibliothèque statique avec quelques objets C++ pour fournir les fonctionnalités spécifiques au périphérique. Le fichier bootable/recovery/default_device.cpp est utilisé par défaut et constitue un bon point de départ à copier lors de l'écriture d'une version de ce fichier pour votre appareil.

Remarque : Vous verrez peut-être un message indiquant Aucune commande ici. Pour basculer le texte, maintenez le bouton d'alimentation enfoncé pendant que vous appuyez sur le bouton d'augmentation du volume. Si vos appareils ne disposent pas des deux boutons, appuyez longuement sur n'importe quel bouton pour basculer 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 de rubrique

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/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). Gardez donc à l'esprit la largeur de l'écran de votre appareil.

Personnaliser CheckKey

Ensuite, définissez l’implémentation RecoveryUI de votre appareil. Cet exemple suppose que le périphérique tardis dispose d'un écran, vous pouvez donc hériter de l'implémentation ScreenRecoveryUI intégrée (voir les instructions pour les appareils sans screen .) La seule fonction à personnaliser à partir de ScreenRecoveryUI est CheckKey() , qui effectue la gestion initiale des clés asynchrones :

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

Constantes CLÉS

Les constantes KEY_* sont définies dans linux/input.h . CheckKey() est appelé peu importe ce qui 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 :

  • BASCULER . Activer ou désactiver l'affichage du menu et/ou du journal texte
  • REDÉMARRER . Redémarrez immédiatement l'appareil
  • IGNORER . Ignorer cette touche
  • MISE EN QUEUE . Mettez cette pression sur cette touche en file d'attente 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'affichage est activé)

CheckKey() est appelé chaque fois qu'un événement key-down est suivi d'un événement key-up pour la même clé. (La séquence d'événements A-down B-down B-up A-up entraîne uniquement 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 ; cela peut être utile pour détecter des séquences de touches. Cet exemple montre une configuration légèrement plus complexe : l'affichage est basculé en maintenant l'alimentation enfoncée et en appuyant sur l'augmentation du volume, et l'appareil peut être redémarré immédiatement en appuyant sur le bouton d'alimentation cinq fois de suite (sans aucune autre touche intermédiaire) :

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;
    }
};

Interface utilisateur de récupération d'écran

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 en images par seconde (FPS) des animations.

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 vous-même animation_fps .

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 une fonction Init() . Pour plus de détails sur les images, consultez Images de l'interface utilisateur de récupération .

Classe d'appareil

Après avoir implémenté RecoveryUI, définissez votre classe de périphérique (sous-classe de la classe Device intégrée). Il doit créer une seule instance de votre classe 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; }

Démarrer la récupération

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

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

Menu d’approvisionnement et de gestion 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; }

PoignéeMenuKey

Ensuite, fournissez une fonction HandleMenuKey() , qui prend en compte une pression sur une touche 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 clé (qui a été préalablement 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 de retour est un entier. Si la valeur est 0 ou supérieure, elle est considérée comme la position d'un élément de menu, qui est invoqué immédiatement (voir la méthode InvokeMenuItem() ci-dessous). Sinon, il peut s'agir de l'une des constantes prédéfinies suivantes :

  • kMise en surbrillance . Déplacer la surbrillance du menu vers l'élément précédent
  • kMise en surbrillance vers le bas . Déplacer la surbrillance du menu vers l'élément suivant
  • kInvokeItem . Invoquer l'élément actuellement en surbrillance
  • kAucuneAction . Ne faites rien avec cette touche

Comme l'implique l'argument visible, HandleMenuKey() est appelé même si le menu n'est pas visible. Contrairement à CheckKey() , il n'est pas appelé lorsque la récupération effectue quelque chose comme l'effacement de données ou l'installation d'un package ; il est appelé uniquement lorsque la récupération est inactive et en attente d'une entrée.

Mécanismes de boule de commande

Si votre appareil dispose d'un mécanisme d'entrée de type trackball (génére des événements d'entrée de type EV_REL et code REL_Y), la récupération synthétise les pressions sur les touches KEY_UP et KEY_DOWN chaque fois que le périphérique d'entrée de type trackball signale un mouvement dans l'axe Y. Tout ce que vous avez à faire est 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 basculer l'affichage.

Touches de modification

Pour vérifier les touches maintenues enfoncées comme modificateurs, appelez la méthode IsKeyPressed() de votre propre objet d'interface utilisateur. Par exemple, sur certains appareils, appuyer sur Alt-W lors de la récupération lancerait un effacement des données, que le menu soit visible ou non. Vous pourriez implémenter comme ceci :

   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 visible est faux, cela n'a pas de sens de renvoyer les valeurs spéciales qui manipulent le menu (déplacer la surbrillance, appeler l'élément en surbrillance) puisque l'utilisateur ne peut pas voir la surbrillance. Cependant, vous pouvez renvoyer les valeurs si vous le souhaitez.

InvoquerMenuItem

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 souhaitez que le système ne fasse rien). C'est ici que vous pouvez fournir des fonctionnalités de récupération supplémentaires au-delà de ce qui se trouve dans le système : ajoutez un élément correspondant dans votre menu, exécutez-le ici lorsque cet élément de menu est invoqué et renvoyez NO_ACTION pour que le système ne fasse rien d'autre.

BuiltinAction contient les valeurs suivantes :

  • PAS D'ACTION . Ne fais rien.
  • REDÉMARRER . Quittez la récupération et redémarrez l'appareil normalement.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD . Installez un package de mise à jour à partir de différents endroits. Pour plus de détails, voir Chargement latéral .
  • WIPE_CACHE . Reformatez uniquement la partition de cache. Aucune confirmation requise car cela est relativement inoffensif.
  • EFFACER LES DONNÉES . Reformatez les partitions de données utilisateur et de cache, également appelée réinitialisation des données 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 d'une récupération via le menu, soit lorsque l'utilisateur a choisi d'effectuer une réinitialisation des données d'usine à partir du système principal). Cette méthode est appelée avant que les données utilisateur et les partitions de cache ne soient effacées. Si votre appareil stocke les 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 pour l'échec, bien qu'actuellement la valeur de retour soit ignorée. Les données utilisateur et les partitions de cache sont effacées, que vous renvoyiez un succès ou un échec.

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

Créer un appareil

Enfin, incluez un 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();
}

Après avoir terminé le fichier recovery_ui.cpp, construisez-le et associez-le à la récupération sur votre appareil. Dans Android.mk, créez une bibliothèque statique contenant uniquement 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'interface utilisateur de récupération

L'interface utilisateur de récupération se compose d'images. Idéalement, les utilisateurs n'interagissent jamais avec l'interface utilisateur : lors d'une mise à jour normale, le téléphone démarre en mode de 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 utilisateur possible est d'appeler le service client.

Une interface contenant uniquement des images évite le besoin de localisation. Cependant, depuis Android 5.0, la mise à jour peut afficher une chaîne de texte (par exemple "Installation de la mise à jour du système...") avec l'image. Pour plus de détails, consultez Texte de récupération localisé .

Android 5.0 et versions ultérieures

L'interface utilisateur de récupération d'Android 5.0 et versions ultérieures utilise deux images principales : l'image d'erreur et l'animation d'installation .

image affichée lors d'une erreur ota

Figure 1. icon_error.png

image affichée lors de l'installation ota

Figure 2. icon_installing.png

L'animation d'installation est représentée sous la forme d'une seule image PNG avec différentes images de l'animation entrelacées par rangée (c'est pourquoi la figure 2 apparaît écrasée). Par exemple, pour une animation de sept images de 200 x 200, créez une seule image de 200 x 1 400 dont la première image correspond aux lignes 0, 7, 14, 21, ... ; le deuxième cadre est constitué des lignes 1, 8, 15, 22, ... ; etc. L'image combinée comprend un morceau de texte qui indique le nombre d'images d'animation et le nombre d'images par seconde (FPS). L'outil bootable/recovery/interlace-frames.py prend un ensemble d'images d'entrée et les combine dans l'image composite nécessaire utilisée par 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 d'images dans l'animation à 0 (l'icône d'erreur n'est pas animée ; c'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 d'erreur (illustré ci-dessus) et l'animation d'installation ainsi que plusieurs images de superposition :

image affichée lors de l'installation ota

Figure 3. icon_installing.png

image affichée en première superposition

Figure 4. icon-installing_overlay01.png

image affichée en septième superposition

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'endroit où la superposition est placée au-dessus de l'image de base :

image composite de l'installation et première superposition

Figure 6. Installation de l'image d'animation 1 (icon_installing.png + icon_installing_overlay01.png)

image composite de l'installation plus septième superposition

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

Les images suivantes sont affichées en dessinant uniquement l'image de superposition suivante au-dessus de ce qui existe déjà ; l'image de base n'est pas redessinée.

Le nombre d'images 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 d'images par défaut, remplacez la méthode Init() dans votre sous-classe pour modifier ces valeurs pour vos images personnalisées (pour plus de détails, voir ScreenRecoveryUI ). Le script bootable/recovery/make-overlay.py peut aider à convertir un ensemble de cadres d'image sous la forme "image de base + images de superposition" nécessaire à 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 d'images dans l'animation à 0 (l'icône d'erreur n'est pas animée ; c'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...") avec l'image. Lorsque le système principal démarre en mode récupération, il transmet les paramètres régionaux actuels de l'utilisateur comme option de ligne de commande pour la récupération. 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 paramètre régional.

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!
  • Effacement... (lors d'un effacement des données/d'une réinitialisation d'usine)
  • Aucune commande (lorsqu'un utilisateur démarre manuellement la récupération)

L'application Android dans bootable/recovery/tools/recovery_l10n/ restitue les localisations d'un message et crée l'image composite. Pour plus de détails sur l'utilisation de cette application, reportez-vous aux commentaires dans bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java .

Lorsqu'un utilisateur démarre manuellement la récupération, les paramètres régionaux peuvent ne pas être disponibles et aucun texte ne s'affiche. Ne rendez pas les messages texte essentiels au processus de récupération.

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

Barres de progression

Des barres de progression peuvent apparaître sous l'image principale (ou l'animation). La barre de progression est réalisée en combinant deux images d'entrée, qui doivent être de 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 de remplissage est affichée à côté de l'extrémité droite de l'image vide pour constituer la barre de progression. La position de la frontière entre les deux images est modifiée pour indiquer la progression. Par exemple, avec les paires d'images d'entrée ci-dessus, affichez :

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 répertoriés ci-dessus ; lorsqu'un fichier est trouvé dans ce répertoire, le système de build 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 pris en charge.

Remarque : sous Android 5.x, si les paramètres régionaux sont connus pour la récupération et sont une langue qui s'écrit de droite à gauche (RTL) (arabe, hébreu, etc.), la barre de progression se remplit de droite à gauche.

Appareils sans écran

Tous les appareils Android n'ont pas d'écran. Si votre appareil est un appareil sans tête ou dispose d'une interface uniquement audio, vous devrez peut-être procéder à une personnalisation plus approfondie de l'interface utilisateur de récupération. Au lieu de créer une sous-classe de ScreenRecoveryUI, sous-classez directement sa classe parent RecoveryUI.

RecoveryUI dispose de méthodes pour 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. pour votre appareil. Peut-être que votre appareil dispose de LED dans lesquelles vous pouvez utiliser différentes couleurs ou motifs de clignotement pour indiquer l'état, ou peut-être pouvez-vous lire de l'audio. (Peut-être que vous ne souhaitez pas du tout prendre en charge un menu ou le mode « affichage de texte » ; vous pouvez empêcher d'y accéder avec les implémentations CheckKey() et HandleMenuKey() qui n'activent jamais l'affichage ni ne sélectionnent un élément de menu. Dans ce cas , la plupart des méthodes RecoveryUI que vous devez fournir peuvent simplement être des talons vides.)

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

Programme de 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 sous 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 de retour est une Value* nouvellement allouée.

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 seront évalués et combien de fois. Ainsi, vous pouvez utiliser des fonctions d'extension pour implémenter vos propres structures de contrôle. Call Evaluate() pour évaluer un argument Expr* , renvoyant un 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 d'appeler éventuellement FreeValue() dessus.

Supposons que la fonction ait besoin de deux arguments : une clé de valeur chaîne et une image de valeur blob. Vous pourriez 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;
    }

Vérifier NULL et libérer les arguments précédemment évalués peut s'avérer fastidieux pour plusieurs arguments. La fonction ReadValueArgs() peut rendre cela plus facile. 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() ne vérifie pas le type, vous devez donc le faire ici ; il est plus pratique de le faire avec une seule instruction if au prix de produire un message d'erreur un peu moins spécifique en cas d'échec. Mais ReadValueArgs() gère l'évaluation de chaque argument et libère tous les arguments précédemment évalués (ainsi que la définition d'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 de retour doit être un objet Value* ; la propriété de cet objet sera transférée à l'appelant. L'appelant s'approprie toutes les données pointées par cette Value* , en particulier le membre de données.

Dans ce cas, vous souhaitez renvoyer une valeur vraie ou fausse pour indiquer le succès. Rappelez-vous la convention selon laquelle la chaîne vide est fausse et toutes les autres chaînes sont vraies . Vous devez mallocer un objet Value avec une copie mallocée de la chaîne constante à renvoyer, puisque 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 pratique StringValue() encapsule une chaîne dans un nouvel objet Value. Utilisez pour écrire le code ci-dessus de manière plus succincte :

   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 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 makefile pour créer une bibliothèque statique avec votre code. (Il s'agit du même makefile utilisé pour personnaliser l'interface utilisateur de récupération dans la section précédente ; votre appareil peut avoir 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 version de 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 qu'elles n'ont pas de fonction Register_ libname ), vous pouvez les lister dans TARGET_RECOVERY_UPDATER_EXTRA_LIBS pour les lier au programme de mise à jour sans appeler leur fonction d'enregistrement (inexistante). Par exemple, si le code spécifique à votre appareil souhaite utiliser zlib pour décompresser les données, vous inclurez 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 à argument unique 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 à permettre aux outils de génération de packages OTA de connaître les données spécifiques à votre appareil et d'émettre des scripts de mise à jour qui incluent des appels à vos fonctions d'extension.

Tout d’abord, informez le système de build d’un blob de données spécifique à l’appareil. En supposant que votre fichier de données se trouve dans device/yoyodyne/tardis/tardis.dat , déclarez ce qui suit 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 périphérique, car tous les fichiers Android.mk de l'arborescence sont chargés quel que soit le périphérique en cours de construction. (Si votre arborescence comprend plusieurs appareils, vous souhaitez uniquement ajouter le fichier tardis.dat lors de la création 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

On les appelle fichiers radio pour des raisons historiques ; ils peuvent n'avoir rien à voir avec la radio de l'appareil (si présente). Ce sont simplement des blobs opaques de données que le système de build copie dans les fichiers cibles .zip utilisés par les outils de génération OTA. Lorsque vous effectuez une construction, 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 (doit être nommé releasetools.py) que les outils peuvent appeler s'ils sont présents. 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 le cas de la génération d’un package OTA incrémentiel. Pour cet exemple, supposons que vous deviez reprogrammer le tardis uniquement lorsque le fichier tardis.dat a changé entre deux builds.

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 des modules

Vous pouvez fournir les fonctions suivantes dans le module (implémenter uniquement celles dont vous avez besoin).

FullOTA_Assertions()
Appelé vers le début de la génération d’un OTA complet. C'est un bon endroit pour émettre des affirmations sur l'état actuel de l'appareil. N'émettez pas de commandes de script qui apportent des modifications au périphérique.
FullOTA_InstallBegin()
Appelé après que toutes les assertions sur l'état du périphérique ont été transmises, mais avant qu'aucune modification n'ait été apportée. Vous pouvez émettre des commandes pour les mises à jour spécifiques à l'appareil qui doivent être exécutées avant que quoi que ce soit d'autre sur l'appareil ne soit modifié.
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()
Similaire à FullOTA_Assertions() mais appelé lors de la génération d'un package de mise à jour incrémentielle.
IncrementalOTA_VerifyBegin()
Appelé une fois que toutes les assertions concernant l’état du périphérique ont été transmises, mais avant qu’aucune modification n’ait été apportée. Vous pouvez émettre des commandes pour les mises à jour spécifiques à l'appareil qui doivent être exécutées avant que quoi que ce soit d'autre sur l'appareil ne soit modifié.
IncrementalOTA_VerifyEnd()
Appelé à la fin de la phase de vérification, 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 n'aient été apportées. Vous pouvez émettre des commandes pour les mises à jour spécifiques à l'appareil qui doivent être exécutées avant que quoi que ce soit d'autre sur l'appareil ne soit modifié.
IncrementalOTA_InstallEnd()
Semblable à son homologue complet du package OTA, ceci est appelé à la fin de la génération du script, après que les commandes de script permettant de mettre à jour les partitions de démarrage et système ont été émises. Vous pouvez également émettre des commandes supplémentaires pour les mises à jour spécifiques à l'appareil.

Remarque : Si l'appareil est hors tension, l'installation OTA peut redémarrer depuis le début. Soyez prêt à faire face à des appareils sur lesquels ces commandes ont déjà été exécutées, entièrement ou partiellement.

Passer des fonctions aux objets d'information

Transmettez des fonctions à un seul objet d'information contenant divers éléments utiles :

  • info.input_zip . (OTA complets uniquement) L'objet zipfile.ZipFile pour les fichiers cibles d'entrée .zip.
  • info.source_zip . (OTA incrémentiels uniquement) L'objet zipfile.ZipFile pour les fichiers cibles sources .zip (la version déjà présente sur l'appareil lors de l'installation du package incrémentiel).
  • info.target_zip . (OTA incrémentiels uniquement) L'objet zipfile.ZipFile pour les fichiers cibles cibles .zip (la version que le package incrémentiel place sur l'appareil).
  • info.output_zip . Package en cours de création ; un objet zipfile.ZipFile ouvert en é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 le texte dans le script. Assurez-vous que le texte de sortie se termine par un point-virgule afin qu'il ne se heurte pas à des commandes émises par la suite.

Pour plus de détails sur l'objet info, reportez-vous à la documentation Python Software Foundation pour les archives ZIP .

Préciser 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 construction du périphérique tardis, le script releasetools.py est inclus dans le fichier .zip des fichiers cibles ( 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 les fichiers cibles .zip, s'il est présent, est préféré à celui de l'arborescence des sources Android. Vous pouvez également spécifier explicitement le chemin d'accès aux extensions spécifiques au périphérique avec l'option -s (ou --device_specific ), qui a la priorité absolue. Cela vous permet de corriger les erreurs, d'apporter des modifications aux extensions releasetools et d'appliquer ces modifications aux anciens fichiers cibles.

Désormais, lorsque vous exécutez ota_from_target_files , il récupère automatiquement le module spécifique au périphérique dans le fichier target_files .zip 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 une liste complète des options, reportez-vous aux commentaires ota_from_target_files dans build/make/tools/releasetools/ota_from_target_files .

Mécanisme de chargement latéral

Recovery dispose d'un mécanisme de chargement latéral permettant d'installer manuellement un package de mise à jour sans le télécharger en direct par le système principal. Le chargement latéral est utile pour déboguer ou apporter des modifications sur des appareils sur lesquels le système principal ne peut pas être démarré.

Historiquement, le chargement latéral se faisait en chargeant des packages à partir de la carte SD de l'appareil ; dans le cas d'un périphérique qui ne démarre pas, le package peut être placé sur la carte SD à l'aide d'un autre ordinateur, puis la carte SD insérée dans le périphérique. Pour prendre en charge les appareils Android sans stockage externe amovible, la récupération prend en charge deux mécanismes supplémentaires pour le chargement latéral : le chargement des packages à partir de la partition de cache et leur chargement via USB à l'aide d'adb.

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

  • APPLY_EXT . Chargez latéralement un package de mise à jour à partir du stockage externe (répertoire /sdcard ). Votre récupération.fstab doit définir le point de montage /sdcard . Ce n'est pas utilisable sur les appareils qui imitent une carte SD avec un lien de symbolique à /data (ou un mécanisme similaire). /data ne sont généralement pas disponibles pour la récupération car elles peuvent être cryptées. L'interface utilisateur de récupération affiche un menu de fichiers .zip dans /sdcard et permet à l'utilisateur d'en sélectionner un.
  • Appliquer_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. À partir du système ordinaire, /cache n'est écrite que par des utilisateurs privilégiés, et si l'appareil n'est pas démarrable, le répertoire /cache ne peut pas être écrit du tout (ce qui fait ce mécanisme d'utilité limitée).
  • Appliquer_adb_sideload . Permet à l'utilisateur d'envoyer un package à l'appareil via un câble USB et l'outil de développement BAD. Lorsque ce mécanisme est invoqué, la récupération démarre sa propre mini-version du démon ADBD pour laisser BAD sur un ordinateur hôte connecté lui parler. Cette mini-version ne prend en charge qu'une seule commande: adb sideload filename . Le fichier nommé est envoyé à partir de la machine hôte à l'appareil, qui le vérifie et l'installe ensuite comme s'il avait été sur le stockage local.

Quelques mises en garde:

  • Seul le transport USB est soutenu.
  • Si votre récupération s'exécute normalement ADBD (généralement vraie pour UserDebug et Eng Builds), cela sera arrêté pendant que l'appareil est en mode ADB Sideload et sera redémarré lorsque le téléchargement ADB a fini de recevoir un package. Alors qu'en mode ADB Sideload, aucune commande ADB autre que le travail sideload ( logcat , reboot , push , pull , shell , etc. tous échouent).
  • Vous ne pouvez pas quitter le mode de téléchargement ADB sur l'appareil. Pour abandonner, vous pouvez envoyer /dev/null (ou tout ce qui n'est pas un package valide) comme package, puis l'appareil ne le vérifiera pas et arrêtera la procédure d'installation. La méthode CheckKey() de RecoveryUi Implementation continuera à être appelée pour des désespares, vous pouvez donc fournir une séquence clé qui redémarre l'appareil et fonctionne en mode ADB Sideload.