Le système de récupération inclut plusieurs hooks permettant d'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 l'appareil tardis produit par le fournisseur yoyodyne.
Carte des partitions
À partir d'Android 2.3, la plate-forme est compatible avec les appareils flash eMMc et le système de fichiers ext4 qui s'exécute sur ces appareils. Il est également compatible avec les appareils flash MTD (Memory Technology Device) et le système de fichiers yaffs2 des versions antérieures.
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 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 de partition:
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 d'installation de cet exemple doivent être définis (les appareils peuvent également ajouter des partitions supplémentaires). Il existe cinq types de systèmes de fichiers compatibles:
- yaffs2
-
Système de fichiers yaffs2 sur un appareil flash MTD. "device" doit être le nom de la partition MTD et doit apparaître dans
/proc/mtd
. - mtd
-
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 installé, mais le point d'installation 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 appareil flash eMMc. "device" doit être le chemin d'accès de l'appareil de bloc.
- eMMC
- Un appareil de bloc eMMc brut, utilisé pour les partitions de démarrage telles que le démarrage et la récupération. Comme pour le type mtd, l'eMMC n'est jamais réellement installé, mais la chaîne du point d'installation permet de localiser l'appareil dans le tableau.
- vfat
-
Un système de fichiers FAT sur un appareil de bloc, généralement pour un stockage externe tel qu'une carte SD. L'appareil est l'appareil de bloc. device2 est un deuxième appareil de bloc que le système tente de monter si l'installation de l'appareil principal échoue (pour la compatibilité avec les cartes SD qui peuvent ou non être formatées avec une table de partitionnement).
Toutes les partitions doivent être montées dans le répertoire racine (c'est-à-dire que la valeur du point d'installation doit commencer par une barre oblique et ne contenir aucune autre barre oblique). Cette restriction ne s'applique qu'à l'installation de systèmes de fichiers en mode récupération. Le système principal est libre de les installer 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èmes 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 de suppression des données/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 à mettre en forme est obtenue en ajoutant la valeur de longueur à la taille réelle de la partition. Par exemple, le paramètre "length=-16384" signifie que les 16 ko de cette partition ne seront pas écrasés lors de son formatage. Cela prend 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 qui ne doit pas être écrasée).
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 au 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.
UI 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 masquées manuelles de 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 pour la copie lorsque vous écrivez une version de ce fichier pour votre appareil.
Remarque:Le message No Command (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 un 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 le fonctionnement du 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 encapsulées). Tenez donc compte de la largeur de l'écran de votre appareil.
Personnaliser CheckKey
Définissez ensuite l'implémentation de RecoveryUI sur 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 (voir les instructions pour les appareils sans écran). La seule fonction à personnaliser à partir de ScreenRecoveryUI est CheckKey()
, qui effectue la gestion initiale des touches asynchrones:
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é quel que soit 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:
- BOUTON D'ACTIVATION/DÉSACTIVATION Activer ou désactiver l'affichage du menu et/ou du journal de texte
- REBOOT (REBOOT). Redémarrez immédiatement l'appareil.
- IGNORE (IGNORER). Ignorer cette pression de touche
- ENQUEUE. Enfilez cette pression de 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 de pression sur une touche est suivi d'un événement de relâchement de la même touche. (La séquence d'événements A-bas B-bas B-haut A-haut ne donne lieu qu'à 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 de touche ci-dessus, si CheckKey(B)
avait appelé IsKeyPressed(A)
, il aurait renvoyé "true".)
CheckKey()
peut maintenir l'état dans sa classe. Cela peut être utile pour détecter des séquences de touches. Cet exemple présente une configuration légèrement plus complexe: l'écran est activé en appuyant de manière prolongée sur le bouton Marche/Arrêt et sur le bouton Volume +. L'appareil peut être redémarré immédiatement en appuyant cinq fois de suite sur le bouton Marche/Arrêt (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; } };
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 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 précédentes d'Android, vous deviez 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 en savoir plus sur les images, consultez la section Images de l'UI de récupération.
Classe d'appareil
Une fois que vous avez implémenté RecoveryUI, définissez votre classe d'appareil (sous-classe de la classe d'appareil intégrée). Elle doit créer une seule instance 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 le 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 pression sur une touche et la visibilité du menu actuelle, et décide de l'action à effectuer:
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 déjà été traité et mis en file d'attente par la méthode CheckKey()
de l'objet UI) et l'état actuel de la visibilité du journal du menu/du texte. La valeur renvoyée est un 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 sélection du menu vers l'élément précédent
- kHighlightDown. Déplacer le curseur de surbrillance du menu vers l'élément suivant
- kInvokeItem. Appeler l'élément actuellement en surbrillance
- kNoAction. Ne rien faire avec cette touche
Comme indiqué par 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 une action telle que l'effacement de données ou l'installation d'un package. Il n'est appelé 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 semblable à un dispositif de pointage (génère des événements d'entrée 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 dispositif de saisie semblable à un dispositif de pointage signale un mouvement sur l'axe Y. Il vous suffit de mapper les événements KEY_UP et KEY_DOWN sur des 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'interface utilisateur. Par exemple, sur certains appareils, appuyer sur Alt+W en mode récupération démarrait un effacement de données, que le menu soit visible ou non. Vous pouvez procéder 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 visible est défini sur "false", il n'est pas logique de renvoyer les valeurs spéciales qui manipulent le menu (déplacer le surlignage, appeler l'élément en surbrillance), car l'utilisateur ne peut pas voir le surlignage. Toutefois, vous pouvez renvoyer les valeurs si vous le souhaitez.
InvokeMenuItem
Ensuite, fournissez une méthode InvokeMenuItem()
qui met en correspondance les positions d'entiers dans le tableau d'éléments renvoyés par GetMenuItems()
avec des actions. Pour le tableau d'éléments de 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 effectue quoi que ce soit). C'est l'endroit idéal pour fournir des fonctionnalités de récupération supplémentaires au-delà de celles du système: ajoutez-en un 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.
- REBOOT (REBOOT). Quittez le mode de 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 en savoir plus, consultez Installation parallèle.
- WIPE_CACHE. Remettez en forme uniquement la partition de cache. Aucune confirmation n'est requise, car cette opération est relativement inoffensive.
- WIPE_DATA. Reformatez les partitions userdata et cache, également appelées "rétablissement de la configuration d'usine". L'utilisateur doit 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 (à partir de la récupération via le menu ou lorsque l'utilisateur a choisi de rétablir la configuration 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 des données utilisateur ailleurs que dans ces deux partitions, vous devez les effacer ici. Vous devez renvoyer 0 pour indiquer un succès et une autre valeur en cas d'échec, bien que la valeur renvoyée soit actuellement ignorée. Les données utilisateur et les partitions de cache sont effacées, que vous renvoyiez une valeur "true" ou "false".
int WipeData() { // ... do something tardis-specific here, if needed .... return 0; }
Marque de l'appareil
Enfin, incluez du code standard à 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(); }
Créer et associer la récupération de l'appareil
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. Idéalement, 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 évite la localisation. 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…") avec l'image. Pour en savoir plus, consultez Texte de récupération localisé.
Android 5.0 ou version ultérieure
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.
![]() Figure 1 : icon_error.png |
![]() Figure 2 : icon_installing.png |
L'animation d'installation est représentée par une seule image PNG, avec différents cadres de l'animation entrelacés par ligne (c'est pourquoi la figure 2 semble écrasée). Par exemple, pour une animation de sept cadres de 200 x 200, créez une seule image de 200 x 1 400, où le premier cadre correspond aux lignes 0, 7, 14, 21, etc., le deuxième cadre aux lignes 1, 8, 15, 22, etc. L'image combinée inclut un bloc de texte qui indique le nombre de cadres d'animation et le nombre de frames 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 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 de frames de l'animation sur 0 (l'icône d'erreur n'est pas animée, il s'agit toujours d'une image statique).
Android 4.x et versions antérieures
L'UI de récupération d'Android 4.x et versions antérieures utilise l'image d'erreur (illustrée ci-dessus) et l'animation d'installation, ainsi que plusieurs images superposées:
![]() Figure 3 : icon_installing.png |
![]() Figure 4 : icon-installing_overlay01.png |
![]() Figure 5 : icon_installing_overlay07.png |
Lors de l'installation, 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:
![]() Figure 6. Installation du frame d'animation 1 (icon_installing.png + icon_installing_overlay01.png) |
![]() Figure 7. Installation du frame d'animation 7 (icon_installing.png + icon_installing_overlay07.png) |
Les images suivantes sont affichées en dessinant uniquement l'image de superposition suivante sur ce qui est déjà là. L'image de base n'est pas redessinée.
Le nombre d'images de 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 en savoir plus, consultez ScreenRecoveryUI). Le script bootable/recovery/make-overlay.py
peut vous aider à convertir un ensemble de cadres d'image au format "image de base + images superposées" requis par 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 de l'animation sur 0 (l'icône d'erreur n'est pas animée, il s'agit toujours d'une image statique).
Texte de récupération localisé
Android 5.x affiche une chaîne de texte (par exemple, "Installing system update…") ainsi que 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 en tant qu'option de ligne de commande à 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 langue.
Exemple d'image de chaînes de texte de récupération:

Figure 8. Texte localisé pour les messages de récupération
Le message de récupération peut afficher les messages suivants:
- Installation de la mise à jour du système…
- Erreur !
- Suppression… (lors d'une suppression des données/réinitialisation d'usine)
- Aucune commande (lorsque l'utilisateur démarre en mode récupération manuellement)
L'application Android dans bootable/recovery/tools/recovery_l10n/
affiche les localisations 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 en mode récupération manuellement, les paramètres régionaux peuvent ne pas être disponibles et aucun texte ne s'affiche. Ne faites pas des messages texte un élément essentiel du processus de récupération.
Remarque:L'interface masquée qui affiche les messages de 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 principale (ou l'animation). La barre de progression est créée en combinant deux images d'entrée, qui doivent être de la même taille:

Figure 9 : progress_empty.png

Figure 10 : progress_fill.png
L'extrémité gauche de l'image remplie s'affiche à côté de l'extrémité droite de l'image vide pour créer 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, affichez:

Figure 11 : Barre de progression à 1%>

Figure 12. Barre de progression à 10%

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 détecté dans ce répertoire, le système de compilation l'utilise de préférence à l'image par défaut correspondante. Seuls les fichiers PNG au format RVB ou RVBA avec une profondeur de couleur de 8 bits sont acceptés.
Remarque:Dans Android 5.x, si les paramètres régionaux sont connus pour la récupération et qu'ils sont une langue 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'écran. Si votre appareil est un appareil sans tête ou dispose d'une interface audio uniquement, vous devrez peut-être personnaliser plus en détail l'UI de récupération. Au lieu de créer une sous-classe de ScreenRecoveryUI, sous-classez directement sa classe parente RecoveryUI.
RecoveryUI dispose de méthodes permettant de gérer des opérations d'interface utilisateur de niveau inférieur, telles que "Activer/Désactiver 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 est peut-être doté de LED qui vous permettent d'utiliser différentes couleurs ou séquences de clignotement pour indiquer l'état, ou vous pouvez diffuser un son. (Vous ne souhaitez peut-être pas du tout prendre en charge un menu ni le mode "Affichage du texte". Vous pouvez empêcher l'accès à ces éléments avec des implémentations CheckKey()
et HandleMenuKey()
qui n'activent jamais l'affichage 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 bouchons 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 abstraite (certaines méthodes sont purement virtuelles et doivent être fournies par des sous-classes), mais elle contient le code permettant de traiter les entrées de clé. Vous pouvez également remplacer cette valeur si votre appareil ne dispose pas de touches 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 pouvant ê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 de l'appel de votre fonction. La logique de votre fonction détermine ceux qui sont évalués et le nombre 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 un Value*
. Si Evaluate()
renvoie NULL, vous devez libérer toutes les ressources que vous détenez et renvoyer immédiatement NULL (ce qui propage les arrêts dans la pile edify). Sinon, vous prenez possession de la valeur renvoyée et vous êtes responsable de l'appeler éventuellement sur FreeValue()
.
Supposons que la fonction nécessite deux arguments: une clé de type chaîne et une image de type blob. Vous pouvez lire les arguments comme suit:
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 peut s'avérer fastidieuse pour plusieurs arguments. La fonction ReadValueArgs()
peut vous y aider. Au lieu du code ci-dessus, vous auriez pu écrire:
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 de type. Vous devez donc le faire ici. Il est plus pratique de le faire avec une 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 la libération de tous les arguments précédemment évalués (et définit 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 cette Value*
, en particulier du membre de données.
Dans cet exemple, vous souhaitez renvoyer une valeur "true" ou "false" pour indiquer que l'opération a réussi. N'oubliez pas que la chaîne vide est false et que toutes les autres chaînes sont true. Vous devez malloc 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 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-le pour écrire le code ci-dessus de manière plus concise:
FreeValue(key); FreeValue(image); return StringValue(strdup(successful ? "t" : "")); }
Pour connecter des fonctions à l'interpréteur edify, fournissez la fonction Register_foo
, où foo 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 de compilation pour créer une bibliothèque statique avec votre code. (Il s'agit du même fichier de compilation utilisé pour personnaliser l'UI de récupération dans la section précédente. Il est possible que les deux bibliothèques statiques soient 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, qui 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,
(elles ne disposent pas d'une fonction Register_libname
), vous pouvez les lister dans TARGET_RECOVERY_UPDATER_EXTRA_LIBS pour les associer à l'outil 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 les 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"))
. Elle 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 en tant que 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 à 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 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 arborescence 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
Ces fichiers sont appelés "fichiers radio" pour des raisons historiques. Ils n'ont rien à voir 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 les fichiers ZIP cibles utilisés par les outils de génération OTA. Lors d'une compilation, tardis.dat est stocké dans target-files.zip sous la forme 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émentiel. Pour cet exemple, supposons que vous ne deviez reprogrammer le tardis que 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 du module
Vous pouvez fournir les fonctions suivantes dans le module (n'implémentez que celles dont vous avez besoin).
FullOTA_Assertions()
- Appelé au début de la génération d'une mise à jour 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é après que toutes les assertions concernant l'état de l'appareil ont été acceptées, 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 s'exécuter avant que tout autre élément de 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 du 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é après que toutes les assertions concernant l'état de l'appareil ont été acceptées, 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 s'exécuter avant que tout autre élément de l'appareil ne soit modifié.
IncrementalOTA_VerifyEnd()
- Appelé à la fin de la phase de validation, lorsque le script a terminé 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 générer du code pour des validations supplémentaires spécifiques à l'appareil.
IncrementalOTA_InstallBegin()
- Appelé après que les fichiers à corriger ont été vérifiés pour s'assurer qu'ils présentent 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 s'exécuter avant que tout autre élément de l'appareil ne soit modifié.
IncrementalOTA_InstallEnd()
- Comme son homologue de package OTA complet, il est appelé à la fin de la génération du script, après que les commandes de script pour 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 n'est plus alimenté, l'installation OTA peut redémarrer depuis le début. Préparez-vous à gérer les appareils sur lesquels ces commandes ont déjà été exécutées, entièrement ou partiellement.
Transmettre des fonctions à des objets d'informations
Transmettez des fonctions à un seul objet d'informations contenant divers éléments utiles:
-
info.input_zip. (OTA complètes uniquement) Objet
zipfile.ZipFile
pour les fichiers .zip de cible d'entrée. -
info.source_zip. (OTA incrémentaux uniquement) Objet
zipfile.ZipFile
pour les fichiers .zip de cible source (compilation déjà présente sur l'appareil lors de l'installation du package incrémentiel). -
info.target_zip. (OTA incrémentaux uniquement) Objet
zipfile.ZipFile
pour les fichiers .zip cibles (le build que le package incrémentiel place sur l'appareil). -
info.output_zip. Package en cours de création ; un objet
zipfile.ZipFile
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 générer du texte dans le script. Assurez-vous que le texte de sortie se termine par un point-virgule afin qu'il ne rencontre pas de 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, le répertoire $(TARGET_DEVICE_DIR)/../common
(device/yoyodyne/common
dans cet exemple) est utilisé par défaut. Il est préférable de définir explicitement l'emplacement du script releasetools.py.
Lors de la compilation de l'appareil 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 le fichier ZIP des fichiers cibles, le cas échéant, 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 a la priorité la plus élevée. Vous pouvez ainsi corriger les erreurs et apporter des modifications dans les extensions de releasetools, puis 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 à 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 sideloading permettant d'installer manuellement un package de mise à jour sans le télécharger en mode OTA par le système principal. Le téléchargement parallèle est utile pour déboguer ou apporter des modifications sur des appareils où le système principal ne peut pas être démarré.
Historiquement, le sideloading consistait à charger des packages à partir de 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 insérée dans l'appareil. Pour prendre en charge les appareils Android sans stockage externe amovible, la récupération prend en charge deux mécanismes supplémentaires de télé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 téléchargement parallèle, la méthode Device::InvokeMenuItem()
de votre appareil peut renvoyer les valeurs suivantes de BuiltinAction:
-
APPLY_EXT. Télécharger un package de mise à jour à partir d'un stockage externe (répertoire
/sdcard
) Votre fichier recovery.fstab doit définir le point d'installation/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. -
APPLIQUER_CACHE. Semblable au chargement d'un paquet à 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 standard,/cache
n'est accessible en écriture que par les utilisateurs privilégiés. Si l'appareil n'est pas amorçable, le répertoire/cache
ne peut pas être écrit 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 appelé, 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 elle. Cette mini-version ne prend en charge qu'une seule commande:
adb sideload filename
. Le fichier nommé est envoyé de la machine hôte à l'appareil, qui le vérifie et l'installe comme s'il avait été stocké localement.
Quelques mises en garde:
- Seul le transport USB est accepté.
-
Si votre récupération exécute adbd normalement (généralement vrai pour les builds userdebug et eng), il sera arrêté lorsque l'appareil sera en mode sideload ADB et redémarré lorsque le sideload ADB aura fini de recevoir un package. En mode téléchargement latéral adb, aucune commande adb autre que
sideload
ne fonctionne (logcat
,reboot
,push
,pull
,shell
, etc. échouent tous). -
Vous ne pouvez pas quitter le mode de téléchargement latéral ADB sur l'appareil. Pour interrompre la procédure, vous pouvez envoyer
/dev/null
(ou tout autre élément qui n'est pas un package valide) en tant que package. L'appareil ne pourra alors pas le valider et arrêtera la procédure d'installation. La méthodeCheckKey()
de l'implémentation de RecoveryUI continuera d'être appelée pour les pressions sur les touches. Vous pouvez donc fournir une séquence de touches qui redémarre l'appareil et fonctionne en mode sideload adb.