El sistema de recuperación incluye varios hooks para insertar código específico del dispositivo, de modo que las actualizaciones OTA también puedan actualizar partes del dispositivo que no sean el sistema Android (p.ej., el procesador de banda base o de radio).
En los siguientes ejemplos y secciones, se personaliza el dispositivo tardis producido por el proveedor yoyodyne.
Mapa de partición
A partir de Android 2.3, la plataforma admite dispositivos flash eMMC y el sistema de archivos ext4 que se ejecuta en esos dispositivos. También admite dispositivos flash de Memory Technology Device (MTD) y el sistema de archivos yaffs2 de versiones anteriores.
El archivo de mapa de particiones se especifica con TARGET_RECOVERY_FSTAB. Este archivo lo usan tanto el binario de recuperación como las herramientas de compilación de paquetes. Puedes especificar el nombre del archivo de mapa en TARGET_RECOVERY_FSTAB en BoardConfig.mk.
Un archivo de mapa de partición de muestra podría verse de la siguiente manera:
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
Con la excepción de /sdcard
, que es opcional, se deben definir todos los puntos de activación de este ejemplo (los dispositivos también pueden agregar particiones adicionales). Existen cinco tipos de sistemas de archivos compatibles:
- yaffs2
-
Un sistema de archivos yaffs2 sobre un dispositivo flash MTD. "device" debe ser el nombre de la partición MTD y debe aparecer en
/proc/mtd
. - mtd
-
Una partición MTD sin procesar, que se usa para particiones de arranque, como boot y recovery. En realidad, el MTD no se activa, pero el punto de activación se usa como clave para ubicar la partición. "device"
debe ser el nombre de la partición MTD en
/proc/mtd
. - ext4
- Un sistema de archivos ext4 sobre un dispositivo flash eMMC. "device" debe ser la ruta de acceso del dispositivo de bloqueo.
- emmc
- Un dispositivo de bloques eMMc sin procesar, que se usa para particiones de arranque, como arranque y recuperación. Al igual que el tipo mtd, el eMMc nunca se activa, pero la cadena del punto de activación se usa para ubicar el dispositivo en la tabla.
- vfat
-
Un sistema de archivos FAT sobre un dispositivo de bloques, por lo general, para almacenamiento externo, como una tarjeta SD. El dispositivo es el dispositivo de bloqueo; device2 es un segundo dispositivo de bloqueo que el sistema intenta activar si falla la activación del dispositivo principal (para la compatibilidad con tarjetas SD que pueden estar formateadas o no con una tabla de particiones).
Todas las particiones deben estar montadas en el directorio raíz (es decir, el valor del punto de montaje debe comenzar con una barra y no tener otras barras). Esta restricción solo se aplica a la activación de sistemas de archivos en la recuperación; el sistema principal puede activarlos en cualquier lugar. Los directorios
/boot
,/recovery
y/misc
deben ser de tipos sin procesar (mtd o emmc), mientras que los directorios/system
,/data
,/cache
y/sdcard
(si están disponibles) deben ser de tipos de sistema de archivos (yaffs2, ext4 o vfat).
A partir de Android 3.0, el archivo recovery.fstab obtiene un campo opcional adicional, options. Actualmente, la única opción definida es length , que te permite especificar de forma explícita la longitud de la partición. Esta longitud se usa cuando se vuelve a formatear la partición (p.ej., para la partición userdata durante una operación de restablecimiento de la configuración de fábrica o borrado de datos, o para la partición del sistema durante la instalación de un paquete OTA completo). Si el valor de longitud es negativo, el tamaño que se debe formatear se obtiene sumando el valor de longitud al tamaño real de la partición. Por ejemplo, establecer "length=-16384" significa que los últimos 16 KB de esa partición no se sobrescribirán cuando se vuelva a formatear la partición. Esto admite funciones como la encriptación de la partición userdata (en la que los metadatos de encriptación se almacenan al final de la partición que no se debe sobrescribir).
Nota: Los campos device2 y options son opcionales, lo que genera ambigüedad en el análisis. Si la entrada del cuarto campo de la línea comienza con un carácter "/", se considera una entrada de device2. Si no comienza con un carácter "/", se considera un campo de options.
Animación de inicio
Los fabricantes de dispositivos pueden personalizar la animación que se muestra cuando se inicia un dispositivo Android. Para ello, crea un archivo .zip organizado y ubicado según las especificaciones del formato de bootanimation.
En el caso de los dispositivos Android Things, puedes subir el archivo comprimido en la consola de Android Things para que las imágenes se incluyan en el producto seleccionado.
Nota: Estas imágenes deben cumplir con los lineamientos de la marca de Android. Para obtener los lineamientos de la marca, consulta la sección de Android del Centro de marketing para socios.
IU de recuperación
Para admitir dispositivos con diferente hardware disponible (botones físicos, LEDs, pantallas, etcétera), Puedes personalizar la interfaz de recuperación para mostrar el estado y acceder a las funciones ocultas que se operan manualmente para cada dispositivo.
Tu objetivo es compilar una pequeña biblioteca estática con un par de objetos de C++ para proporcionar la funcionalidad específica del dispositivo. El archivo bootable/recovery/default_device.cpp
se usa de forma predeterminada y es un buen punto de partida para copiar cuando escribes una versión de este archivo para tu dispositivo.
Nota: Es posible que veas un mensaje que diga No Command aquí. Para alternar el texto, mantén presionado el botón de encendido mientras presionas el botón para subir el volumen. Si el dispositivo no tiene ambos botones, mantén presionado cualquier botón para alternar el texto.
device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h> #include "common.h" #include "device.h" #include "screen_ui.h"
Funciones de encabezado y de elementos
La clase Device requiere funciones para devolver encabezados y elementos que aparecen en el menú de recuperación oculto. Los encabezados describen cómo operar el menú (es decir, los controles para cambiar o seleccionar el elemento destacado).
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 };
Nota: Las líneas largas se truncan (no se ajustan), por lo que debes tener en cuenta el ancho de la pantalla de tu dispositivo.
Personaliza CheckKey
A continuación, define la implementación de RecoveryUI de tu dispositivo. En este ejemplo, se supone que el dispositivo tardis tiene una pantalla, por lo que puedes heredar de la implementación integrada de ScreenRecoveryUI (consulta las instrucciones para dispositivos sin pantalla). La única función que se puede personalizar de ScreenRecoveryUI es CheckKey()
, que controla la clave asíncrona inicial:
class TardisUI : public ScreenRecoveryUI { public: virtual KeyAction CheckKey(int key) { if (key == KEY_HOME) { return TOGGLE; } return ENQUEUE; } };
Constantes KEY
Las constantes KEY_* se definen en linux/input.h
. CheckKey()
se llama sin importar lo que suceda en el resto de la recuperación: cuando el menú está desactivado, cuando está activado, durante la instalación del paquete, durante la limpieza de los datos del usuario, etcétera. Puede devolver una de las cuatro constantes:
- TOGGLE. Activa o desactiva la visualización del menú o del registro de texto
- REINICIAR. Reinicia el dispositivo de inmediato.
- IGNORAR. Ignorar esta pulsación de tecla
- ENQUEUE. Pone en cola esta presión de tecla para que se consuma de forma síncrona (es decir, por el sistema de menú de recuperación si la pantalla está habilitada).
Se llama a CheckKey()
cada vez que un evento de tecla presionada es seguido por un evento de tecla liberada para la misma tecla. (La secuencia de eventos A-down B-down B-up A-up solo genera una llamada a CheckKey(B)
). CheckKey()
puede llamar a IsKeyPressed()
para averiguar si se mantienen presionadas otras teclas. (En la secuencia anterior de eventos de teclas, si CheckKey(B)
hubiera llamado a IsKeyPressed(A)
, habría devuelto verdadero).
CheckKey()
puede mantener el estado en su clase, lo que puede ser útil para detectar secuencias de teclas. En este ejemplo, se muestra una configuración un poco más compleja: la pantalla se activa manteniendo presionado el botón de encendido y presionando el botón para subir el volumen, y el dispositivo se puede reiniciar de inmediato presionando el botón de encendido cinco veces seguidas (sin ninguna otra tecla intermedia):
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
Cuando usas tus propias imágenes (ícono de error, animación de instalación, barras de progreso) con ScreenRecoveryUI, puedes establecer la variable animation_fps
para controlar la velocidad en fotogramas por segundo (FPS) de las animaciones.
Nota: El script interlace-frames.py
actual te permite almacenar la información de animation_fps
en la imagen. En versiones anteriores de Android, era necesario establecer animation_fps
por tu cuenta.
Para establecer la variable animation_fps
, anula la función ScreenRecoveryUI::Init()
en tu subclase. Establece el valor y, luego, llama a la función parent Init()
para completar la inicialización. El valor predeterminado (20 FPS) corresponde a las imágenes de recuperación predeterminadas. Cuando usas estas imágenes, no es necesario que proporciones una función Init()
. Para obtener detalles sobre las imágenes, consulta Imágenes de la IU de recuperación.
Clase de dispositivo
Después de implementar RecoveryUI, define la clase del dispositivo (una subclase de la clase Device integrada). Debe crear una sola instancia de tu clase de IU y devolverla desde la función GetUI()
:
class TardisDevice : public Device { private: TardisUI* ui; public: TardisDevice() : ui(new TardisUI) { } RecoveryUI* GetUI() { return ui; }
StartRecovery
Se llama al método StartRecovery()
al inicio de la recuperación, después de que se inicializó la IU y después de que se analizaron los argumentos, pero antes de que se realice cualquier acción. La implementación predeterminada no hace nada, por lo que no es necesario que la proporciones en tu subclase si no tienes nada que hacer:
void StartRecovery() { // ... do something tardis-specific here, if needed .... }
Suministra y administra el menú de recuperación
El sistema llama a dos métodos para obtener la lista de líneas de encabezado y la lista de elementos. En esta implementación, se devuelven los arrays estáticos definidos en la parte superior del archivo:
const char* const* GetMenuHeaders() { return HEADERS; } const char* const* GetMenuItems() { return ITEMS; }
HandleMenuKey
A continuación, proporciona una función HandleMenuKey()
, que toma una pulsación de tecla y la visibilidad actual del menú, y decide qué acción tomar:
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; }
El método toma un código de tecla (que se procesó y puso en cola previamente con el método CheckKey()
del objeto de IU) y el estado actual de la visibilidad del registro de menú o texto. El valor que se devuelve es un número entero. Si el valor es 0 o superior, se toma como la posición de un elemento de menú, que se invoca de inmediato (consulta el método InvokeMenuItem()
a continuación). De lo contrario, puede ser una de las siguientes constantes predefinidas:
- kHighlightUp. Mover el resaltado del menú al elemento anterior
- kHighlightDown. Mover el resaltado del menú al siguiente elemento
- kInvokeItem. Invocar el elemento destacado actualmente
- kNoAction. No hacer nada con esta presión de tecla
Como lo implica el argumento visible, se llama a HandleMenuKey()
incluso si el menú no está visible. A diferencia de CheckKey()
, no se llama mientras la recuperación realiza alguna acción, como borrar datos o instalar un paquete. Solo se llama cuando la recuperación está inactiva y espera la entrada.
Mecanismos de la bola de seguimiento
Si tu dispositivo tiene un mecanismo de entrada similar a una trackball (genera eventos de entrada con el tipo EV_REL y el código REL_Y), la recuperación sintetiza las pulsaciones de teclas KEY_UP y KEY_DOWN cada vez que el dispositivo de entrada similar a una trackball informa movimiento en el eje Y. Todo lo que debes hacer es asignar los eventos KEY_UP y KEY_DOWN a las acciones del menú. Esta asignación no se realiza para CheckKey()
, por lo que no puedes usar los movimientos de la trackball como activadores para reiniciar o alternar la pantalla.
Teclas modificadoras
Para verificar si se mantienen presionadas las teclas como modificadores, llama al método IsKeyPressed()
de tu propio objeto de IU. Por ejemplo, en algunos dispositivos, presionar Alt + W en el modo de recuperación iniciaría un borrado de datos, ya sea que el menú estuviera visible o no. Podrías implementar el código de la siguiente manera:
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 } ... }
Nota: Si visible es falso, no tiene sentido devolver los valores especiales que manipulan el menú (mover el elemento destacado, invocar el elemento destacado), ya que el usuario no puede ver el elemento destacado. Sin embargo, puedes devolver los valores si lo deseas.
InvokeMenuItem
A continuación, proporciona un método InvokeMenuItem()
que asigne posiciones de números enteros en el array de elementos que devuelve GetMenuItems()
a acciones. Para el array de elementos en el ejemplo de la tardis, usa lo siguiente:
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; } }
Este método puede devolver cualquier miembro de la enumeración BuiltinAction para indicarle al sistema que realice esa acción (o el miembro NO_ACTION si quieres que el sistema no haga nada). Este es el lugar para proporcionar funcionalidad de recuperación adicional más allá de lo que se encuentra en el sistema: agrega un elemento para ello en tu menú, ejecútalo aquí cuando se invoque ese elemento del menú y devuelve NO_ACTION para que el sistema no haga nada más.
BuiltinAction contiene los siguientes valores:
- NO_ACTION. No realizar ninguna acción.
- REINICIAR. Sal de la recuperación y reinicia el dispositivo normalmente.
- APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Instala un paquete de actualización desde varios lugares. Para obtener más información, consulta Carga lateral.
- WIPE_CACHE. Solo reformatea la partición de caché. No se requiere confirmación, ya que esta acción es relativamente inofensiva.
- WIPE_DATA. Volver a formatear las particiones de datos del usuario y de caché, lo que también se conoce como restablecimiento de la configuración de fábrica Se le pide al usuario que confirme esta acción antes de continuar.
El último método, WipeData()
, es opcional y se llama cada vez que se inicia una operación de borrado de datos (ya sea desde la recuperación a través del menú o cuando el usuario elige restablecer la configuración de fábrica desde el sistema principal). Se llama a este método antes de que se borren las particiones de datos y caché del usuario. Si tu dispositivo almacena datos del usuario en cualquier otro lugar que no sean esas dos particiones, debes borrarlos aquí. Debes devolver 0 para indicar que la operación se realizó correctamente y otro valor si falló, aunque actualmente se ignora el valor de devolución. Se borran las particiones de caché y datos del usuario, ya sea que devuelvas un resultado de éxito o de error.
int WipeData() { // ... do something tardis-specific here, if needed .... return 0; }
Fabricante del dispositivo
Por último, incluye código estándar al final del archivo recovery_ui.cpp para la función make_device()
que crea y devuelve una instancia de tu clase Device:
class TardisDevice : public Device { // ... all the above methods ... }; Device* make_device() { return new TardisDevice(); }
Compila y vincula la recuperación del dispositivo
Después de completar el archivo recovery_ui.cpp, compílalo y vincúlalo a la recuperación en tu dispositivo. En Android.mk, crea una biblioteca estática que contenga solo este archivo 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)
Luego, en la configuración de la placa para este dispositivo, especifica tu biblioteca estática como el valor de TARGET_RECOVERY_UI_LIB.
device/yoyodyne/tardis/BoardConfig.mk [...] # device-specific extensions to the recovery UI TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis
Imágenes de la IU de recuperación
La interfaz de usuario de recuperación consta de imágenes. Idealmente, los usuarios nunca interactúan con la IU: Durante una actualización normal, el teléfono se inicia en el modo de recuperación, completa la barra de progreso de la instalación y se reinicia en el nuevo sistema sin que el usuario realice ninguna acción. En caso de un problema de actualización del sistema, la única acción del usuario que se puede realizar es llamar al equipo de atención al cliente.
Una interfaz solo con imágenes elimina la necesidad de localización. Sin embargo, a partir de Android 5.0, la actualización puede mostrar una cadena de texto (p. ej., "Instalando actualización del sistema…") junto con la imagen. Para obtener más información, consulta Texto de recuperación localizado.
Android 5.0 y versiones posteriores
La IU de recuperación de Android 5.0 y versiones posteriores usa dos imágenes principales: la imagen de error y la animación de instalación.
![]() Figura 1. icon_error.png |
![]() Figura 2: ícono de icon_installing.png |
La animación de instalación se representa como una sola imagen PNG con diferentes fotogramas de la animación entrelazados por fila (por eso, la Figura 2 se ve comprimida). Por ejemplo, para una animación de siete fotogramas de 200 x 200, crea una sola imagen de 200 x 1,400 en la que el primer fotograma sean las filas 0, 7, 14, 21, etc.; el segundo fotograma sean las filas 1, 8, 15, 22, etc.; y así sucesivamente. La imagen combinada incluye un fragmento de texto que indica la cantidad de fotogramas de la animación y la cantidad de fotogramas por segundo (FPS). La herramienta bootable/recovery/interlace-frames.py
toma un conjunto de fotogramas de entrada y los combina en la imagen compuesta necesaria que se usa para la recuperación.
Las imágenes predeterminadas están disponibles en diferentes densidades y se encuentran en bootable/recovery/res-$DENSITY/images
(p.ej.,
bootable/recovery/res-hdpi/images
). Para usar una imagen estática durante la instalación, solo debes proporcionar la imagen icon_installing.png y establecer la cantidad de fotogramas en la animación en 0 (el ícono de error no está animado; siempre es una imagen estática).
Android 4.x y versiones anteriores
La IU de recuperación de Android 4.x y versiones anteriores usa la imagen de error (que se muestra arriba) y la animación de instalación, además de varias imágenes superpuestas:
![]() Figura 3. icon_installing.png |
![]() Figura 4: ícono de icon-installing_overlay01.png |
![]() Figura 5: ícono de icon_installing_overlay07.png |
Durante la instalación, la pantalla se construye dibujando la imagen icon_installing.png y, luego, dibujando uno de los fotogramas de superposición sobre ella con el desplazamiento adecuado. Aquí, se superpone un cuadro rojo para destacar dónde se coloca la superposición sobre la imagen base:
![]() Figura 6: Instalación del fotograma de animación 1 (icon_installing.png + icon_installing_overlay01.png) |
![]() Figura 7: Instalación del fotograma de animación 7 (icon_installing.png + icon_installing_overlay07.png) |
Los fotogramas posteriores se muestran dibujando solo la siguiente imagen superpuesta sobre lo que ya está allí; la imagen base no se vuelve a dibujar.
La cantidad de fotogramas en la animación, la velocidad deseada y las compensaciones en X y en Y de la superposición en relación con la base se establecen mediante variables de miembro de la clase ScreenRecoveryUI. Cuando uses imágenes personalizadas en lugar de imágenes predeterminadas, anula el método Init()
en tu subclase para cambiar estos valores por los de tus imágenes personalizadas (para obtener más detalles, consulta ScreenRecoveryUI). La secuencia de comandos bootable/recovery/make-overlay.py
puede ayudar a convertir un conjunto de fotogramas de imágenes al formato de "imagen base + imágenes superpuestas" que necesita la recuperación, incluido el cálculo de las compensaciones necesarias.
Las imágenes predeterminadas se encuentran en bootable/recovery/res/images
. Para usar una imagen estática durante la instalación, solo debes proporcionar la imagen icon_installing.png y establecer la cantidad de fotogramas en la animación en 0 (el ícono de error no está animado; siempre es una imagen estática).
Texto de recuperación localizado
Android 5.x muestra una cadena de texto (p.ej., "Instalando actualización del sistema…") junto con la imagen. Cuando el sistema principal se inicia en el modo de recuperación, pasa la configuración regional actual del usuario como una opción de línea de comandos para la recuperación. Para cada mensaje que se muestra, la recuperación incluye una segunda imagen compuesta con cadenas de texto renderizadas previamente para ese mensaje en cada configuración regional.
Imagen de ejemplo de cadenas de texto de recuperación:

Figura 8: Texto localizado para los mensajes de recuperación
El texto de recuperación puede mostrar los siguientes mensajes:
- Instalando actualización del sistema…
- Error
- Borrando… (cuando se borran los datos o se restablece la configuración de fábrica)
- Sin comando (cuando un usuario inicia la recuperación de forma manual)
La app para Android en bootable/recovery/tools/recovery_l10n/
renderiza las localizaciones de un mensaje y crea la imagen compuesta. Para obtener detalles sobre el uso de esta app, consulta los comentarios en bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
.
Cuando un usuario inicia el modo de recuperación de forma manual, es posible que la configuración regional no esté disponible y no se muestre ningún texto. No hagas que los mensajes de texto sean fundamentales para el proceso de recuperación.
Nota: La interfaz oculta que muestra mensajes de registro y permite al usuario seleccionar acciones del menú solo está disponible en inglés.
Barras de progreso
Las barras de progreso pueden aparecer debajo de la imagen (o animación) principal. La barra de progreso se crea combinando dos imágenes de entrada, que deben tener el mismo tamaño:

Figura 9. progress_empty.png

Figura 10: progress_fill.png
El extremo izquierdo de la imagen de relleno se muestra junto al extremo derecho de la imagen de vacío para formar la barra de progreso. La posición del límite entre las dos imágenes cambia para indicar el progreso. Por ejemplo, con los pares de imágenes de entrada anteriores, se muestra lo siguiente:

Figura 11: Barra de progreso al 1%>

Figura 12: Barra de progreso al 10%

Figura 13: Barra de progreso al 50%
Puedes proporcionar versiones específicas para cada dispositivo de estas imágenes colocándolas en (en este ejemplo) device/yoyodyne/tardis/recovery/res/images
. Los nombres de archivo deben coincidir con los que se indicaron anteriormente. Cuando se encuentra un archivo en ese directorio, el sistema de compilación lo usa en lugar de la imagen predeterminada correspondiente. Solo se admiten archivos PNG en formato RGB o RGBA con una profundidad de color de 8 bits.
Nota: En Android 5.x, si la recuperación conoce la configuración regional y es un idioma de derecha a izquierda (RTL) (árabe, hebreo, etc.), la barra de progreso se completa de derecha a izquierda.
Dispositivos sin pantallas
No todos los dispositivos Android tienen pantallas. Si tu dispositivo es un electrodoméstico sin pantalla o tiene una interfaz solo de audio, es posible que debas personalizar la IU de recuperación de forma más exhaustiva. En lugar de crear una subclase de ScreenRecoveryUI, crea una subclase de su clase superior RecoveryUI directamente.
RecoveryUI tiene métodos para controlar operaciones de IU de nivel inferior, como "alternar la pantalla", "actualizar la barra de progreso", "mostrar el menú", "cambiar la selección del menú", etcétera. Puedes anular estos métodos para proporcionar una interfaz adecuada para tu dispositivo. Quizás tu dispositivo tenga luces LED en las que puedes usar diferentes colores o patrones de parpadeo para indicar el estado, o quizás puedas reproducir audio. (Quizás no quieras admitir un menú o el modo de "visualización de texto" en absoluto; puedes impedir el acceso a ellos con implementaciones de CheckKey()
y HandleMenuKey()
que nunca activen la pantalla ni seleccionen un elemento de menú. En este caso, muchos de los métodos de RecoveryUI que debes proporcionar pueden ser solo stubs vacíos.
Consulta bootable/recovery/ui.h
para ver la declaración de RecoveryUI y conocer los métodos que debes admitir. RecoveryUI es abstracto (algunos métodos son virtuales puros y deben proporcionarse por subclases), pero contiene el código para procesar las entradas de teclas. También puedes anular ese comportamiento si tu dispositivo no tiene teclas o si quieres procesarlas de otra manera.
Google Updater
Puedes usar código específico del dispositivo en la instalación del paquete de actualización proporcionando tus propias funciones de extensión que se pueden llamar desde tu secuencia de comandos del actualizador. Esta es una función de ejemplo para el dispositivo tardis:
device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h> #include <string.h> #include "edify/expr.h"
Todas las funciones de extensión tienen la misma firma. Los argumentos son el nombre con el que se llamó a la función, una cookie State*
, la cantidad de argumentos entrantes y un array de punteros Expr*
que representan los argumentos. El valor que se devuelve es un Value*
recién asignado.
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); }
Tus argumentos no se evalúan en el momento en que se llama a tu función. La lógica de tu función determina cuáles de ellos se evalúan y cuántas veces. Por lo tanto, puedes usar funciones de extensión para implementar tus propias estructuras de control. Call Evaluate()
para evaluar un argumento Expr*
y devolver un Value*
. Si Evaluate()
devuelve NULL, debes liberar los recursos que tengas y devolver NULL de inmediato (esto propaga las anulaciones hacia arriba en la pila de edify). De lo contrario, te haces cargo del valor que se muestra y eres responsable de llamar a FreeValue()
en algún momento.
Supongamos que la función necesita dos argumentos: una clave con valor de cadena y una imagen con valor de BLOB. Podrías leer los argumentos de la siguiente manera:
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; }
Verificar si hay valores NULL y liberar los argumentos evaluados previamente puede ser tedioso para varios argumentos. La función ReadValueArgs()
puede facilitar esta tarea. En lugar del código anterior, podrías haber escrito lo siguiente:
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()
no realiza la verificación de tipos, por lo que debes hacerlo aquí. Es más conveniente hacerlo con una sola instrucción if a costa de producir un mensaje de error algo menos específico cuando falla. Sin embargo, ReadValueArgs()
sí controla la evaluación de cada argumento y libera todos los argumentos evaluados previamente (además de establecer un mensaje de error útil) si falla alguna de las evaluaciones. Puedes usar una función de conveniencia ReadValueVarArgs()
para evaluar una cantidad variable de argumentos (muestra un array de Value*
).
Después de evaluar los argumentos, realiza el trabajo de la función:
// 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 ...
El valor que se muestra debe ser un objeto Value*
; la propiedad de este objeto pasará al llamador. El llamador se hace cargo de los datos a los que apunta este Value*
, específicamente el miembro de datos.
En este caso, deseas devolver un valor verdadero o falso para indicar que la operación se realizó correctamente. Recuerda la convención de que la cadena vacía es falsa y todas las demás cadenas son verdaderas. Debes asignar memoria con malloc a un objeto Value con una copia asignada con malloc de la cadena constante que se devolverá, ya que el llamador free()
ambos. No olvides llamar a FreeValue()
en los objetos que obtuviste evaluando tus argumentos.
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 función de conveniencia StringValue()
encapsula una cadena en un nuevo objeto Value.
Se usa para escribir el código anterior de forma más concisa:
FreeValue(key); FreeValue(image); return StringValue(strdup(successful ? "t" : "")); }
Para conectar funciones al intérprete de Edify, proporciona la función Register_foo
, en la que foo es el nombre de la biblioteca estática que contiene este código. Llama a RegisterFunction()
para registrar cada función de extensión. Por convención, nombra las funciones específicas del dispositivo device.whatever
para evitar conflictos con las futuras funciones integradas que se agreguen.
void Register_librecovery_updater_tardis() { RegisterFunction("tardis.reprogram", ReprogramTardisFn); }
Ahora puedes configurar el archivo makefile para compilar una biblioteca estática con tu código. (Este es el mismo archivo makefile que se usó para personalizar la IU de recuperación en la sección anterior; es posible que tu dispositivo tenga ambas bibliotecas estáticas definidas aquí).
device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS) LOCAL_SRC_FILES := recovery_updater.c LOCAL_C_INCLUDES += bootable/recovery
El nombre de la biblioteca estática debe coincidir con el nombre de la función Register_libname
que contiene.
LOCAL_MODULE := librecovery_updater_tardis include $(BUILD_STATIC_LIBRARY)
Por último, configura la compilación de recuperación para extraer tu biblioteca. Agrega tu biblioteca a TARGET_RECOVERY_UPDATER_LIBS (que puede contener varias bibliotecas; todas se registran).
Si tu código depende de otras bibliotecas estáticas que no son extensiones de Edify (es decir,
no tienen una función Register_libname
), puedes incluirlas en TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vincularlas al actualizador sin llamar a su función de registro (inexistente). Por ejemplo, si tu código específico del dispositivo quisiera usar zlib para descomprimir datos, incluirías libz aquí.
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 +=
Ahora, las secuencias de comandos del actualizador en tu paquete de OTA pueden llamar a tu función como a cualquier otra. Para reprogramar tu dispositivo de viaje en el tiempo, el script de actualización podría contener: tardis.reprogram("the-key", package_extract_file("tardis-image.dat"))
. Esto usa la versión de un solo argumento de la función integrada package_extract_file()
, que devuelve el contenido de un archivo extraído del paquete de actualización como un BLOB para producir el segundo argumento de la nueva función de extensión.
Generación de paquetes OTA
El componente final consiste en hacer que las herramientas de generación de paquetes OTA conozcan los datos específicos de tu dispositivo y emitan secuencias de comandos del actualizador que incluyan llamadas a tus funciones de extensión.
Primero, haz que el sistema de compilación conozca un BLOB de datos específico del dispositivo. Si suponemos que tu archivo de datos está en device/yoyodyne/tardis/tardis.dat
, declara lo siguiente en el archivo AndroidBoard.mk de tu dispositivo:
device/yoyodyne/tardis/AndroidBoard.mk
[...] $(call add-radio-file,tardis.dat)
También puedes colocarlo en un archivo Android.mk, pero, en ese caso, debe estar protegido por una verificación del dispositivo, ya que todos los archivos Android.mk del árbol se cargan independientemente del dispositivo que se esté compilando. (Si tu árbol incluye varios dispositivos, solo querrás que se agregue el archivo tardis.dat cuando compiles el dispositivo 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
Estos se denominan archivos de radio por razones históricas, pero es posible que no tengan nada que ver con la radio del dispositivo (si está presente). Son simplemente BLOB opacos de datos que el sistema de compilación copia en el archivo .zip de target-files que usan las herramientas de generación de OTA. Cuando realizas una compilación, tardis.dat se almacena en target-files.zip como RADIO/tardis.dat
. Puedes llamar a add-radio-file
varias veces para agregar tantos archivos como quieras.
Módulo de Python
Para extender las herramientas de lanzamiento, escribe un módulo de Python (debe llamarse releasetools.py) al que las herramientas puedan llamar si está presente. Ejemplo:
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"));""")
Una función independiente controla el caso de generar un paquete de OTA incremental. Para este ejemplo, supongamos que necesitas reprogramar la TARDIS solo cuando el archivo tardis.dat haya cambiado entre dos compilaciones.
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"));""")
Funciones del módulo
Puedes proporcionar las siguientes funciones en el módulo (implementa solo las que necesites).
FullOTA_Assertions()
- Se llama cerca del inicio de la generación de una OTA completa. Este es un buen lugar para emitir aserciones sobre el estado actual del dispositivo. No emitas comandos de secuencias de comandos que realicen cambios en el dispositivo.
FullOTA_InstallBegin()
- Se llama después de que se hayan pasado todas las aserciones sobre el estado del dispositivo, pero antes de que se hayan realizado cambios. Puedes emitir comandos para actualizaciones específicas del dispositivo que deben ejecutarse antes de que se cambie cualquier otra cosa en el dispositivo.
FullOTA_InstallEnd()
- Se llama al final de la generación de la secuencia de comandos, después de que se emiten los comandos de la secuencia de comandos para actualizar las particiones del sistema y de arranque. También puedes emitir comandos adicionales para las actualizaciones específicas del dispositivo.
IncrementalOTA_Assertions()
-
Similar a
FullOTA_Assertions()
, pero se llama cuando se genera un paquete de actualización incremental. IncrementalOTA_VerifyBegin()
- Se llama después de que se pasan todas las aserciones sobre el estado del dispositivo, pero antes de que se realicen cambios. Puedes emitir comandos para actualizaciones específicas del dispositivo que deben ejecutarse antes de que se cambie cualquier otra cosa en el dispositivo.
IncrementalOTA_VerifyEnd()
- Se llama al final de la fase de verificación, cuando la secuencia de comandos terminó de confirmar que los archivos que va a modificar tienen el contenido inicial esperado. En este punto, no se cambió nada en el dispositivo. También puedes emitir código para verificaciones adicionales específicas del dispositivo.
IncrementalOTA_InstallBegin()
- Se llama después de que se verificó que los archivos que se parcharán tienen el estado anterior esperado, pero antes de que se realicen los cambios. Puedes emitir comandos para actualizaciones específicas del dispositivo que deben ejecutarse antes de que se cambie cualquier otra cosa en el dispositivo.
IncrementalOTA_InstallEnd()
- Al igual que su contraparte del paquete OTA completo, se llama al final de la generación de la secuencia de comandos, después de que se emiten los comandos de la secuencia de comandos para actualizar las particiones de inicio y del sistema. También puedes emitir comandos adicionales para actualizaciones específicas del dispositivo.
Nota: Si el dispositivo se queda sin energía, es posible que la instalación inalámbrica se reinicie desde el principio. Prepárate para lidiar con dispositivos en los que estos comandos ya se ejecutaron, ya sea de forma completa o parcial.
Pasa funciones a objetos de información
Pasa funciones a un solo objeto de información que contiene varios elementos útiles:
-
info.input_zip. (Solo para OTA completas) Es el objeto
zipfile.ZipFile
para el archivo .zip de target-files de entrada. -
info.source_zip. (Solo para OTA incrementales) Es el objeto
zipfile.ZipFile
para el archivo .zip de target-files de origen (la compilación que ya está en el dispositivo cuando se instala el paquete incremental). -
info.target_zip. (Solo para OTA incrementales) Es el objeto
zipfile.ZipFile
para el archivo .zip de target-files de destino (la compilación que el paquete incremental coloca en el dispositivo). -
info.output_zip. Paquete que se está creando; un objeto
zipfile.ZipFile
abierto para escritura. Usa common.ZipWriteStr(info.output_zip, filename, data) para agregar un archivo al paquete. -
info.script. Objeto de secuencia de comandos al que puedes agregar comandos. Llama a
info.script.AppendExtra(script_text)
para generar texto en la secuencia de comandos. Asegúrate de que el texto de salida termine con un punto y coma para que no se ejecuten los comandos emitidos posteriormente.
Para obtener detalles sobre el objeto info, consulta la documentación de Python Software Foundation sobre archivos ZIP.
Cómo especificar la ubicación del módulo
Especifica la ubicación de la secuencia de comandos releasetools.py de tu dispositivo en el archivo BoardConfig.mk:
device/yoyodyne/tardis/BoardConfig.mk
[...] TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis
Si no se configura TARGET_RELEASETOOLS_EXTENSIONS, se establece de forma predeterminada en el directorio $(TARGET_DEVICE_DIR)/../common
(device/yoyodyne/common
en este ejemplo). Lo mejor es definir de forma explícita la ubicación de la secuencia de comandos releasetools.py.
Cuando se compila el dispositivo tardis, la secuencia de comandos releasetools.py se incluye en el archivo .zip de target-files (META/releasetools.py
).
Cuando ejecutas las herramientas de lanzamiento (img_from_target_files
o ota_from_target_files
), se prefiere la secuencia de comandos releasetools.py en el archivo .zip de target-files, si está presente, a la del árbol de origen de Android. También puedes especificar de forma explícita la ruta de acceso a las extensiones específicas del dispositivo con la opción -s
(o --device_specific
), que tiene la máxima prioridad. Esto te permite
corregir errores y realizar cambios en las extensiones de releasetools, y aplicar esos cambios a los archivos
target antiguos.
Ahora, cuando ejecutas ota_from_target_files
, se selecciona automáticamente el módulo específico del dispositivo del archivo .zip target_files y se usa cuando se generan paquetes de 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
Como alternativa, puedes especificar extensiones específicas del dispositivo cuando ejecutas 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
Nota: Para obtener una lista completa de las opciones, consulta los comentarios de ota_from_target_files
en build/make/tools/releasetools/ota_from_target_files
.
Mecanismo de transferencia
La recuperación tiene un mecanismo de transferencia para instalar manualmente un paquete de actualización sin descargarlo de forma inalámbrica a través del sistema principal. El sideloading es útil para depurar o realizar cambios en dispositivos en los que no se puede iniciar el sistema principal.
Históricamente, la transferencia local se ha realizado cargando paquetes desde la tarjeta SD del dispositivo. En el caso de un dispositivo que no se inicia, el paquete se puede colocar en la tarjeta SD con otra computadora y, luego, insertar la tarjeta SD en el dispositivo. Para admitir dispositivos Android sin almacenamiento externo extraíble, la recuperación admite dos mecanismos adicionales para la instalación de prueba: carga de paquetes desde la partición de caché y carga de paquetes a través de USB con adb.
Para invocar cada mecanismo de carga lateral, el método Device::InvokeMenuItem()
de tu dispositivo puede devolver los siguientes valores de BuiltinAction:
-
APPLY_EXT. Transfiere un paquete de actualización desde el almacenamiento externo (directorio
/sdcard
). Tu archivo recovery.fstab debe definir el punto de activación/sdcard
. Esto no se puede usar en dispositivos que emulan una tarjeta SD con un vínculo simbólico a/data
(o algún mecanismo similar). Por lo general,/data
no está disponible para la recuperación porque puede estar encriptado. La IU de recuperación muestra un menú de archivos .zip en/sdcard
y permite que el usuario seleccione uno. -
APPLY_CACHE. Es similar a cargar un paquete desde
/sdcard
, excepto que, en su lugar, se usa el directorio/cache
(que siempre está disponible para la recuperación). En el sistema normal, solo los usuarios con privilegios pueden escribir en/cache
, y si el dispositivo no se puede iniciar, no se puede escribir en el directorio/cache
(lo que hace que este mecanismo sea de utilidad limitada). -
APPLY_ADB_SIDELOAD. Permite al usuario enviar un paquete al dispositivo a través de un cable USB y la herramienta de desarrollo de adb. Cuando se invoca este mecanismo, la recuperación inicia su propia versión reducida del daemon adbd para permitir que adb en una computadora host conectada se comunique con él. Esta versión mini solo admite un comando:
adb sideload filename
. El archivo con nombre se envía desde la máquina host al dispositivo, que luego lo verifica y lo instala como si estuviera en el almacenamiento local.
Algunas advertencias:
- Solo se admite el transporte por USB.
-
Si la recuperación ejecuta adbd con normalidad (lo que suele ocurrir en las compilaciones userdebug y eng), se cerrará mientras el dispositivo esté en modo de carga lateral de adb y se reiniciará cuando la carga lateral de adb haya terminado de recibir un paquete. En el modo de transferencia lateral de adb, no funciona ningún comando de adb que no sea
sideload
(logcat
,reboot
,push
,pull
,shell
, etc. fallan). -
No puedes salir del modo de transferencia de ADB en el dispositivo. Para anular la operación, puedes enviar
/dev/null
(o cualquier otro elemento que no sea un paquete válido) como paquete. Luego, el dispositivo no podrá verificarlo y detendrá el procedimiento de instalación. Se seguirá llamando al métodoCheckKey()
de la implementación de RecoveryUI para las pulsaciones de teclas, por lo que puedes proporcionar una secuencia de teclas que reinicie el dispositivo y funcione en el modo de transferencia lateral de adb.