Código específico para el dispositivo

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 del sistema Android (p.ej., el procesador de radio o de banda base).

En las siguientes secciones y ejemplos, se personaliza el dispositivo tardis que produce el proveedor yoyodyne.

Mapa de particiones

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 tecnología de memoria (MTD) y el sistema de archivos yaffs2 de versiones anteriores.

TARGET_RECOVERY_FSTAB especifica el archivo de mapa de particiones, que usan el binario de recuperación y 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 particiones 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 en este ejemplo (los dispositivos también pueden agregar particiones adicionales). Existen cinco tipos de sistemas de archivos compatibles:

yaffs2
Un sistema de archivos yaffs2 en un dispositivo flash MTD. “device” debe ser el nombre de la partición de MTD y debe aparecer en /proc/mtd.
mtd
Una partición MTD sin procesar, que se usa para particiones que se pueden iniciar, como el inicio y la recuperación. 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 en un dispositivo flash eMMC. "device" debe ser la ruta de acceso del dispositivo de almacenamiento en bloque.
emmc
Es un dispositivo de almacenamiento en bloque eMMc sin procesar que se usa para particiones de inicio, como el inicio y la recuperación. Al igual que el tipo mtd, eMMc nunca se activa, pero se usa la cadena de punto de activación para ubicar el dispositivo en la tabla.
vfat
Un sistema de archivos FAT en un dispositivo de almacenamiento en bloques, por lo general, para almacenamiento externo, como una tarjeta SD. El dispositivo es el dispositivo de almacenamiento en bloques. device2 es un segundo dispositivo de almacenamiento en bloques que el sistema intenta activar si falla la activación del dispositivo principal (para la compatibilidad con tarjetas SD que pueden o no estar formateadas con una tabla de particiones).

Todas las particiones deben estar activadas en el directorio raíz (es decir, el valor del punto de activación debe comenzar con una barra diagonal y no tener otras barras diagonales). Esta restricción solo se aplica a la activación de sistemas de archivos en el modo de recuperación. El sistema principal puede activarlos en cualquier lugar. Los directorios /boot, /recovery y /misc deben ser de tipo sin procesar (mtd o emmc), mientras que los directorios /system, /data, /cache y /sdcard (si están disponibles) deben ser de tipo 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 de datos de usuario durante una operación de limpieza de datos o restablecimiento de la configuración de fábrica, o para la partición del sistema durante la instalación de un paquete OTA completo). Si el valor de longitud es negativo, se agrega el valor de longitud al tamaño real de la partición para obtener el tamaño que se usará para dar formato. Por ejemplo, configurar "length=-16384" significa que los últimos 16 K de esa partición no se reemplazarán cuando se vuelva a formatear. Esto admite funciones como la encriptación de la partición de datos del usuario (en la que los metadatos de encriptación se almacenan al final de la partición que no se debe reemplazar).

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 device2. Si la entrada no comienza con un carácter "/", se considera un campo 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 en el formato de animación de inicio.

En el caso de los dispositivos Android Things, puedes subir el archivo comprimido en Android Things Console para que se incluyan las imágenes en el producto seleccionado.

Nota: Estas imágenes deben cumplir con los lineamientos de desarrollo de la marca de Android. Para ver los lineamientos de la marca, consulta la sección de Android en el Centro de comarketing.

IU de recuperación

Para admitir dispositivos con diferentes hardware disponibles (botones físicos, LED, pantallas, etcétera), Puedes personalizar la interfaz de recuperación para mostrar el estado y acceder a las funciones ocultas que se operan de forma manual para cada dispositivo.

Tu objetivo es compilar una pequeña biblioteca estática con un par de objetos 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 activar o desactivar el texto, mantén presionado el botón de encendido mientras presionas el botón de subir volumen. Si tu dispositivo no tiene ambos botones, mantén presionado cualquier botón para activar o desactivar 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 artículo

La clase Device requiere funciones para mostrar 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 unen), así que ten en cuenta el ancho de la pantalla del 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 desde ScreenRecoveryUI es CheckKey(), que realiza el manejo inicial de claves asíncronas:

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.Se llama a CheckKey() sin importar lo que esté sucediendo 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 mostrar una de las cuatro constantes:

  • BOTÓN DE ACTIVACIÓN. Activa o desactiva la visualización del menú o el registro de texto
  • REINICIAR. Reinicia el dispositivo de inmediato
  • IGNORAR. Ignora esta presión de tecla
  • ENQUEUE: Encola esta pulsació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 abajo es seguido por un evento de tecla arriba para la misma tecla. (La secuencia de eventos A-abajo B-abajo B-arriba A-arriba solo genera una llamada a CheckKey(B)). CheckKey() puede llamar a IsKeyPressed() para averiguar si se están manteniendo presionadas otras teclas. (En la secuencia de eventos de teclas anterior, si CheckKey(B) llamara a IsKeyPressed(A), habría mostrado un valor verdadero).

CheckKey() puede mantener el estado en su clase, lo que puede ser útil para detectar secuencias de claves. En este ejemplo, se muestra una configuración un poco más compleja: para activar la pantalla, mantén presionado el botón de encendido y presiona el botón para subir el volumen. Para reiniciar el dispositivo de inmediato, presiona el botón de encendido cinco veces seguidas (sin que intervengan otras teclas):

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 configurar la variable animation_fps para controlar la velocidad en fotogramas por segundo (FPS) de las animaciones.

Nota: La secuencia de comandos interlace-frames.py actual te permite almacenar la información de animation_fps en la imagen. En versiones anteriores de Android, era necesario configurar animation_fps por tu cuenta.

Para configurar 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 necesitas proporcionar una función Init(). Para obtener más información sobre las imágenes, consulta Imágenes de la IU de recuperación.

Clase del dispositivo

Después de tener una implementación de RecoveryUI, define tu clase de dispositivo (subclase de la clase de dispositivo integrada). Debería crear una sola instancia de tu clase de IU y mostrarla 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 comienzo de la recuperación, después de que se inicializa la IU y después de que se analizan los argumentos, pero antes de que se realice cualquier acción. La implementación predeterminada no hace nada, por lo que no necesitas proporcionarla en tu subclase si no tienes nada que hacer:

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

Proporciona 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, muestra 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 del menú actual, y decide qué acción realizar:

   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 clave (que el método CheckKey() del objeto de la IU procesó y puso en cola anteriormente) y el estado actual de la visibilidad del registro de menú o texto. El valor que se muestra es un número entero. Si el valor es 0 o superior, se considera 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. Cómo mover el elemento destacado del menú al elemento anterior
  • kHighlightDown. Cómo mover el elemento destacado del menú al siguiente
  • kInvokeItem. Invoca el elemento destacado actualmente
  • kNoAction. No hacer nada con esta pulsación de tecla

Como lo insinúa el argumento visible, se llama a HandleMenuKey() incluso si el menú no es visible. A diferencia de CheckKey(), no se llama mientras la recuperación realiza una acción, como limpiar datos o instalar un paquete. Solo se llama cuando la recuperación está inactiva y espera una entrada.

Mecanismos de bola de seguimiento

Si el dispositivo tiene un mecanismo de entrada similar al de una bola de seguimiento (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 la bola de seguimiento 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 ocurre para CheckKey(), por lo que no puedes usar los movimientos del trackball como activadores para reiniciar o activar la pantalla.

Teclas modificadoras

Para verificar si se mantienen presionadas 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 iniciaba una limpieza de datos, independientemente de si el menú era visible o no. Puedes implementarlo 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 mostrar 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 mostrar 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 muestra GetMenuItems() a acciones. Para el array de elementos del ejemplo de 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 mostrar 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 una funcionalidad de recuperación adicional más allá de lo que está en el sistema: agrega un elemento para él en tu menú, ejecútalo aquí cuando se invoque ese elemento de menú y muestra 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 del modo de recuperación y reinicia el dispositivo de forma normal.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Instala un paquete de actualización desde varios lugares. Para obtener más información, consulta Sideloading.
  • WIPE_CACHE. Solo reformatea la partición de caché. No se requiere confirmación, ya que es relativamente inofensivo.
  • WIPE_DATA. Vuelve a formatear las particiones de datos del usuario y de la caché, también conocidas como restablecimiento de datos de fábrica. Se le solicita 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 limpieza de datos (ya sea desde la recuperación a través del menú o cuando el usuario elige restablecer los datos de fábrica desde el sistema principal). Se llama a este método antes de borrar los datos del usuario y las particiones de la caché. Si tu dispositivo almacena datos del usuario en cualquier lugar que no sea en esas dos particiones, debes borrarlos aquí. Debes mostrar 0 para indicar que se realizó correctamente y otro valor para indicar que falló, aunque actualmente se ignora el valor que se muestra. Las particiones de datos del usuario y de la caché se borran, ya sea que se muestre un estado de éxito o de error.

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

Marca del dispositivo

Por último, incluye un texto de plantilla al final del archivo recovery_ui.cpp para la función make_device() que crea y muestra una instancia de tu clase Device:

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

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

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 solo contenga 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 de 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 modo de recuperación, se completa la barra de progreso de la instalación y se reinicia en el sistema nuevo sin la entrada del usuario. En caso de un problema con la actualización del sistema, la única acción que puede realizar el usuario es llamar al servicio de atención al cliente.

Una interfaz de solo imagen obvia la necesidad de localización. Sin embargo, a partir de Android 5.0, la actualización puede mostrar una cadena de texto (p. ej., "Se está instalando la 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.

imagen que se muestra durante un error de actualización OTA

Figura 1: icon_error.png

imagen que se muestra durante la instalación inalámbrica

Figura 2: 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 lo que la Figura 2 aparece comprimida). Por ejemplo, para una animación de siete fotogramas de 200 × 200, crea una sola imagen de 200 × 1,400 en la que el primer fotograma sea las filas 0, 7, 14, 21, etc.; el segundo fotograma sea las filas 1, 8, 15, 22, etc.; etcétera. La imagen combinada incluye un fragmento de texto que indica la cantidad de fotogramas de 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 usa 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:

imagen que se muestra durante la instalación inalámbrica

Figura 3: icon_installing.png

imagen que se muestra como primera superposición

Figura 4: icon-installing_overlay01.png

imagen que se muestra como séptima superposición

Figura 5: icon_installing_overlay07.png

Durante la instalación, la pantalla en pantalla se construye dibujando la imagen icon_installing.png y, luego, uno de los marcos de superposición sobre ella en el desplazamiento correcto. Aquí, se superpone un cuadro rojo para destacar dónde se coloca la superposición sobre la imagen base:

Imagen compuesta de la instalación más la primera superposición

Figura 6: Marco de animación de instalación 1 (icon_installing.png + icon_installing_overlay01.png)

imagen compuesta de la instalación más la séptima superposición

Figura 7: Se está instalando el marco de animación 7 (icon_installing.png + icon_installing_overlay07.png).

Para mostrar los fotogramas posteriores, se dibuja solo la siguiente imagen superpuesta sobre lo que ya está allí; no se vuelve a dibujar la imagen base.

Las variables miembro de la clase ScreenRecoveryUI establecen la cantidad de fotogramas en la animación, la velocidad deseada y los desplazamientos en X e Y de la superposición en relación con la base. Cuando uses imágenes personalizadas en lugar de imágenes predeterminadas, anula el método Init() en tu subclase para cambiar estos valores para tus imágenes personalizadas (para obtener más información, consulta ScreenRecoveryUI). La secuencia de comandos bootable/recovery/make-overlay.py puede ayudar a convertir un conjunto de fotogramas de imagen en la forma "imagen base + imágenes superpuestas" que necesita la recuperación, incluido el cálculo de los desplazamientos necesarios.

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., "Installing system update…") junto con la imagen. Cuando el sistema principal se inicia en modo de recuperación, pasa la configuración regional actual del usuario como una opción de línea de comandos a la recuperación. Para que se muestre cada mensaje, 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:

imagen 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 la 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 la 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 los 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 ser del mismo tamaño:

barra de progreso vacía

Figura 9: progress_empty.png

barra de progreso completa

Figura 10: progress_fill.png

El extremo izquierdo de la imagen completa se muestra junto al extremo derecho de la imagen vacía para crear 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:

barra de progreso en 1%

Figura 11: Barra de progreso en 1%>

barra de progreso en 10%

Figura 12: Barra de progreso al 10%

barra de progreso al 50%

Figura 13: Barra de progreso al 50%

Puedes proporcionar versiones específicas del 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 enumeraron 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 configuración regional es conocida por la recuperación 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 dispositivo sin cabeza o tiene una interfaz de solo audio, es posible que debas personalizar más la IU de recuperación. En lugar de crear una subclase de ScreenRecoveryUI, crea una subclase directamente de su clase superior RecoveryUI.

RecoveryUI tiene métodos para controlar operaciones de IU de nivel inferior, como "activar la pantalla", "actualizar la barra de progreso", "mostrar el menú", "cambiar la selección del menú", etcétera. Puedes anularlos para proporcionar una interfaz adecuada para tu dispositivo. Tal vez tu dispositivo tenga luces LED en las que puedes usar diferentes colores o patrones de parpadeo para indicar el estado, o tal vez puedas reproducir audio. (Quizás no quieras admitir un menú ni el modo "text display". Puedes evitar 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 stubs vacíos.)

Consulta bootable/recovery/ui.h para ver la declaración de RecoveryUI y saber qué métodos debes admitir. RecoveryUI es abstracto (algunos métodos son puramente virtuales y deben proporcionarse a través de subclases), pero contiene el código para procesar entradas clave. También puedes anularlo si tu dispositivo no tiene teclas o si deseas procesarlas de forma diferente.

Google Updater

Para usar código específico del dispositivo en la instalación del paquete de actualización, proporciona tus propias funciones de extensión a las que se pueda llamar desde la secuencia de comandos del actualizador. Esta es una función de muestra para el dispositivo tardis:

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

#include "edify/expr.h"

Cada función de extensión tiene 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 muestra es un Value* asignado recientemente.

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 evaluaron en el momento en que se llamó a la función. La lógica de la función determina cuáles 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 mostrar un Value*. Si Evaluate() muestra un valor NULL, debes liberar los recursos que tengas y mostrar un valor NULL de inmediato (esto propaga las interrupciones en la pila de edify). De lo contrario, adquieres la propiedad del valor que se muestra y serás 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. Puedes leer argumentos como los siguientes:

   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 verificación de NULL y la liberación de argumentos evaluados anteriormente puede ser tediosa para varios argumentos. La función ReadValueArgs() puede facilitar esto. 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 sentencia if a costa de producir un mensaje de error menos específico cuando falla. Sin embargo, ReadValueArgs() controla la evaluación de cada argumento y libera todos los argumentos evaluados anteriormente (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 se pasará al llamador. El llamador se apropia de los datos a los que apunta esta Value*, en particular, el miembro de datos.

En este caso, deseas mostrar un valor verdadero o falso para indicar que se realizó correctamente. Recuerda la convención de que la cadena vacía es false y todas las demás cadenas son true. Debes asignar memoria a un objeto Value con una copia de la cadena constante que se mostrará, 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() une una cadena en un nuevo objeto Value. Usa lo siguiente 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. Según la convención, asigna el nombre device.whatever a las funciones específicas del dispositivo para evitar conflictos con las funciones integradas que se agreguen en el futuro.

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 de configuración make 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 enumerarlas en TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vincularlas al actualizador sin llamar a su función de registro (no existente). 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 +=

Las secuencias de comandos del actualizador en tu paquete OTA ahora pueden llamar a tu función como cualquier otra. Para reprogramar tu dispositivo Tardis, la secuencia de comandos de actualización podría contener lo siguiente: 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 muestra 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 es hacer que las herramientas de generación de paquetes OTA conozcan tus datos específicos del dispositivo y emitan secuencias de comandos de actualización 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 tu archivo de datos está en device/yoyodyne/tardis/tardis.dat, declara lo siguiente en 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 luego debe estar protegido por una verificación de dispositivos, ya que todos los archivos Android.mk del árbol se cargan sin importar qué dispositivo se compila. (Si tu árbol incluye varios dispositivos, solo debes agregar 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

Se llaman archivos de radio por razones históricas y es posible que no tengan nada que ver con la radio del dispositivo (si está presente). Son simplemente fragmentos opacos de datos que el sistema de compilación copia en el archivo .zip de destino 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 desees.

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 OTA incremental. En este ejemplo, supongamos que necesitas reprogramar el Tardis solo cuando el archivo tardis.dat cambió 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 secuencia de comandos que realicen cambios en el dispositivo.
FullOTA_InstallBegin()
: Se llama después de que se aprueben 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 otro elemento del dispositivo.
FullOTA_InstallEnd()
: Se lo llama al final de la generación de la secuencia de comandos, después de que se hayan emitido 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.
IncrementalOTA_Assertions()
Es similar a FullOTA_Assertions(), pero se lo llama cuando se genera un paquete de actualización incremental.
IncrementalOTA_VerifyBegin()
Se llama después de que se aprueben 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 otro elemento del dispositivo.
IncrementalOTA_VerifyEnd()
Se llama al final de la fase de verificación, cuando la secuencia de comandos termina de confirmar que los archivos que tocará 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 verifica que los archivos que se van a corregir tienen el estado antes 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 otro elemento del dispositivo.
IncrementalOTA_InstallEnd()
Al igual que su contraparte de paquete OTA completo, se lo llama al final de la generación de la secuencia de comandos, después de que se hayan emitido 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 pierde energía, es posible que la instalación inalámbrica se reinicie desde el principio. Prepárate para lidiar con dispositivos en los que ya se ejecutaron estos comandos, de forma total o parcial.

Pasa funciones a objetos de información

Pasa funciones a un solo objeto de información que contenga varios elementos útiles:

  • info.input_zip. (Solo OTA completas) El objeto zipfile.ZipFile para el archivo .zip de los archivos de destino de entrada.
  • info.source_zip. (Solo OTA incrementales) El objeto zipfile.ZipFile para los archivos de destino .zip de la fuente (la compilación que ya está en el dispositivo cuando se instala el paquete incremental)
  • info.target_zip. (Solo OTA incrementales) El objeto zipfile.ZipFile para el archivo .zip de destino objetivo (la compilación que el paquete incremental coloca en el dispositivo).
  • info.output_zip. Se está creando el paquete; se abrió un objeto zipfile.ZipFile para escribir. Usa common.ZipWriteStr(info.output_zip, filename, data) para agregar un archivo al paquete.
  • info.script. Es el objeto de secuencia de comandos al que puedes adjuntar comandos. Llama a info.script.AppendExtra(script_text) para que se muestre el texto en la secuencia de comandos. Asegúrate de que el texto de salida finalice con un punto y coma para que no se ejecute en los comandos emitidos posteriormente.

Para obtener detalles sobre el objeto info, consulta la documentación de la Python Software Foundation para archivos ZIP.

Especifica 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 establece TARGET_RELEASETOOLS_EXTENSIONS, se establece de forma predeterminada en el directorio $(TARGET_DEVICE_DIR)/../common (device/yoyodyne/common en este ejemplo). Es mejor 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 los archivos de destino (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, en lugar de 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 prioridad más alta. Esto te permite corregir errores y realizar cambios en las extensiones de releasetools y aplicar esos cambios a los archivos de destino anteriores.

Ahora, cuando ejecutas ota_from_target_files, este detecta automáticamente el módulo específico del dispositivo del archivo .zip target_files y lo usa cuando genera paquetes 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 que el sistema principal lo descargue de forma inalámbrica. El sideloading es útil para depurar o realizar cambios en dispositivos en los que no se puede iniciar el sistema principal.

Históricamente, la carga lateral se realizaba a través de la carga de 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 a través de USB con adb.

Para invocar cada mecanismo de carga lateral, el método Device::InvokeMenuItem() de tu dispositivo puede mostrar los siguientes valores de BuiltinAction:

  • APPLY_EXT. Transfiere un paquete de actualización desde el almacenamiento externo (directorio /sdcard). El archivo recovery.fstab debe definir el punto de activación /sdcard . No se puede usar en dispositivos que emulan una tarjeta SD con un symlink 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 le permite al usuario seleccionar uno.
  • APPLY_CACHE. Es similar a cargar un paquete desde /sdcard, excepto que se usa el directorio /cache (que siempre está disponible para la recuperación). Desde el sistema normal, solo los usuarios con privilegios pueden escribir en /cache . Si el dispositivo no se puede iniciar, no se puede escribir en el directorio /cache (lo que hace que este mecanismo tenga una utilidad limitada).
  • APPLY_ADB_SIDELOAD. Permite que el usuario envíe 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 mini 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 de 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 USB.
  • Si la recuperación ejecuta adbd de forma normal (por lo general, es cierto para las compilaciones de userdebug y eng), se apagará mientras el dispositivo esté en el modo de transferencia lateral de adb y se reiniciará cuando la transferencia lateral de adb termine de recibir un paquete. Mientras estás en el modo de carga lateral de adb, no funcionan comandos de adb que no sean sideload ( logcat, reboot, push, pull , shell, etc.).
  • No puedes salir del modo de transferencia de ADB en el dispositivo. Para abortar, puedes enviar /dev/null (o cualquier otro elemento que no sea un paquete válido) como el paquete. Luego, el dispositivo no podrá verificarlo y detendrá el procedimiento de instalación. Se seguirá llamando al método CheckKey() de la implementación de RecoveryUI para las pulsaciones de teclas, de modo que puedas proporcionar una secuencia de teclas que reinicie el dispositivo y funcione en el modo de carga lateral de adb.