Código específico del dispositivo

El sistema de recuperación incluye varios ganchos para insertar código específico del dispositivo para que las actualizaciones OTA también puedan actualizar partes del dispositivo que no sean el sistema Android (por ejemplo, la banda base o el procesador de radio).

Las siguientes secciones y ejemplos personalizan el dispositivo tardis producido por el proveedor de 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 es compatible con dispositivos flash Memory Technology Device (MTD) y el sistema de archivos yaffs2 de versiones anteriores.

El archivo de asignación de particiones se especifica mediante TARGET_RECOVERY_FSTAB; este archivo lo utilizan tanto el binario de recuperación como las herramientas de creación de paquetes. Puede 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 tener este aspecto:

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, todos los puntos de montaje en este ejemplo deben definirse (los dispositivos también pueden agregar particiones adicionales). Hay cinco tipos de sistemas de archivos admitidos:

yaffs2
Un sistema de archivos yaffs2 encima de un dispositivo flash MTD. "dispositivo" debe ser el nombre de la partición MTD y debe aparecer en /proc/mtd .
mtd
Una partición MTD sin procesar, utilizada para particiones de arranque como arranque y recuperación. MTD en realidad no está montado, pero el punto de montaje se usa como clave para ubicar la partición. "dispositivo" debe ser el nombre de la partición MTD en /proc/mtd .
ext4
Un sistema de archivos ext4 encima de un dispositivo flash eMMc. "dispositivo" debe ser la ruta del dispositivo de bloque.
emmc
Un dispositivo de bloque eMMc sin formato, utilizado para particiones de arranque, como arranque y recuperación. Similar al tipo mtd, eMMc nunca se monta realmente, pero la cadena de punto de montaje se usa para ubicar el dispositivo en la tabla.
gordo
Un sistema de archivos FAT encima de un dispositivo de bloque, generalmente para almacenamiento externo, como una tarjeta SD. El dispositivo es el dispositivo de bloque; device2 es un segundo dispositivo de bloque que el sistema intenta montar si falla el montaje del dispositivo principal (para compatibilidad con tarjetas SD que pueden o no estar formateadas con una tabla de particiones).

Todas las particiones deben montarse en el directorio raíz (es decir, el valor del punto de montaje debe comenzar con una barra inclinada y no tener otras barras inclinadas). Esta restricción se aplica solo al montaje de sistemas de archivos en recuperación; el sistema principal es libre de montarlos en cualquier lugar. Los directorios /boot , /recovery y /misc deben ser tipos sin formato (mtd o emmc), mientras que los directorios /system , /data , /cache y /sdcard (si está disponible) deben ser tipos de sistemas de archivos (yaffs2, ext4 o vgrasa).

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 le permite especificar explícitamente la longitud de la partición. Esta longitud se utiliza cuando se reformatea la partición (p. ej., para la partición de datos de usuario durante una operación de borrado de datos/restablecimiento 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, el tamaño a formatear se toma sumando el valor de longitud al tamaño real de la partición. Por ejemplo, configurar "longitud = -16384" significa que los últimos 16k de esa partición no se sobrescribirán cuando se reformatee esa partición. Esto admite funciones como el cifrado de la partición de datos de usuario (donde los metadatos de cifrado se almacenan al final de la partición que no deben sobrescribirse).

Nota: Los campos device2 y options son opcionales, lo que crea ambigüedad en el análisis. Si la entrada en el cuarto campo de la línea comienza con un carácter '/', se considera una entrada de dispositivo2 ; si la entrada no comienza con un carácter '/', se considera un campo de opciones .

Animación de inicio

Los fabricantes de dispositivos tienen la capacidad de personalizar la animación que se muestra cuando se inicia un dispositivo Android. Para ello, construye un archivo .zip organizado y ubicado según las especificaciones en formato bootanimation .

Para dispositivos Android Things , puede cargar 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 las pautas de la marca Android.

IU de recuperación

Para admitir dispositivos con diferente hardware disponible (botones físicos, LED, pantallas, etc.), puede personalizar la interfaz de recuperación para mostrar el estado y acceder a las funciones ocultas operadas manualmente para cada dispositivo.

Su objetivo es crear 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 al escribir una versión de este archivo para su dispositivo.

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 elemento

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, controles para cambiar/seleccionar el elemento resaltado).

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 están truncadas (no envueltas), así que tenga en cuenta el ancho de la pantalla de su dispositivo.

Personalización de CheckKey

A continuación, defina la implementación de RecoveryUI de su dispositivo. Este ejemplo asume que el dispositivo tardis tiene una pantalla, por lo que puede heredar de la implementación integrada de ScreenRecoveryUI (vea las instrucciones para dispositivos sin pantalla ). La única función para personalizar desde ScreenRecoveryUI es CheckKey() , que realiza el manejo asincrónico inicial de claves:

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

constantes CLAVE

Las constantes KEY_* se definen en linux/input.h . Se 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 el borrado de datos de usuario, etc. Puede devolver una de cuatro constantes:

  • CAMBIAR . Activar o desactivar la visualización del menú y/o registro de texto
  • REINICIAR . Inmediatamente reinicie el dispositivo
  • IGNORAR Ignorar esta pulsación de tecla
  • ENQUEUE . Ponga en cola esta pulsación de tecla para que se consuma sincrónicamente (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 pulsación de tecla es seguido por un evento de pulsación de tecla para la misma tecla. (La secuencia de eventos A-abajo B-abajo B-arriba A-arriba solo da como resultado que se llame a CheckKey(B) . CheckKey() puede llamar a IsKeyPressed() , para averiguar si se mantienen presionadas otras teclas. (En la secuencia anterior de eventos clave, si CheckKey(B) llamara IsKeyPressed(A) habría devuelto verdadero).

CheckKey() puede mantener el estado en su clase; esto puede ser útil para detectar secuencias de teclas. Este ejemplo muestra una configuración un poco más compleja: la pantalla se alterna manteniendo presionado el botón de encendido y presionando subir el volumen, y el dispositivo se puede reiniciar inmediatamente presionando el botón de encendido cinco veces seguidas (sin otras teclas intermedias):

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 usa sus propias imágenes (icono de error, animación de instalación, barras de progreso) con ScreenRecoveryUI, puede configurar la variable animation_fps para controlar la velocidad en cuadros por segundo (FPS) de las animaciones.

Nota: El script actual interlace-frames.py le permite almacenar la información animation_fps en la propia imagen. En versiones anteriores de Android, era necesario configurar animation_fps usted mismo.

Para configurar la variable animation_fps , invalide la ScreenRecoveryUI::Init() en su subclase. Establezca el valor, luego llame 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; al usar estas imágenes, no necesita proporcionar una función Init() . Para obtener detalles sobre las imágenes, consulte Imágenes de IU de recuperación .

Clase de dispositivo

Una vez que tenga una implementación de RecoveryUI, defina su clase de dispositivo (subclase de la clase de dispositivo integrada). Debería crear una única instancia de su clase de interfaz de usuario y devolverla desde la función GetUI() :

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

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

    RecoveryUI* GetUI() { return ui; }

Iniciar la recuperación

Se llama al método StartRecovery() al comienzo de la recuperación, después de inicializar la interfaz de usuario y de analizar los argumentos, pero antes de realizar cualquier acción. La implementación predeterminada no hace nada, por lo que no necesita proporcionar esto en su subclase si no tiene nada que hacer:

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

Suministro y gestión del menú de recuperación

El sistema llama a dos métodos para obtener la lista de líneas de encabezado y la lista de artículos. En esta implementación, devuelve las matrices estáticas definidas en la parte superior del archivo:

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

HandleMenuKey

A continuación, proporcione una función HandleMenuKey() , que toma una pulsación de tecla y la visibilidad del menú actual, 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 clave (que ha sido previamente procesado y puesto en cola por el método CheckKey() del objeto UI) y el estado actual de la visibilidad del registro de menú/texto. El valor devuelto 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 inmediatamente (consulte el método InvokeMenuItem() continuación). De lo contrario, puede ser una de las siguientes constantes predefinidas:

  • kResaltar hacia arriba . Mover el resaltado del menú al elemento anterior
  • kResaltar Abajo . Mover el resaltado del menú al siguiente elemento
  • kInvokeItem . Invocar el elemento resaltado actualmente
  • kNoAcción . No hacer nada con esta pulsación de tecla

Como implica el argumento visible, se llama a HandleMenuKey() incluso si el menú no está visible. A diferencia CheckKey() , no se llama mientras la recuperación está haciendo algo, como borrar datos o instalar un paquete; solo se llama cuando la recuperación está inactiva y esperando una entrada.

Mecanismos de bola de seguimiento

Si su dispositivo tiene un mecanismo de entrada similar a 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 tecla KEY_UP y KEY_DOWN cada vez que el dispositivo de entrada similar a una bola de seguimiento informa movimiento en el eje Y. Todo lo que necesita hacer es asignar los eventos KEY_UP y KEY_DOWN a las acciones del menú. Esta asignación no ocurre con CheckKey() , por lo que no puede usar los movimientos de la bola de seguimiento como disparadores para reiniciar o alternar la pantalla.

Teclas modificadoras

Para verificar si las teclas se mantienen presionadas como modificadores, llame al método IsKeyPressed() de su propio objeto de interfaz de usuario. Por ejemplo, en algunos dispositivos, presionar Alt-W en la recuperación iniciaría un borrado de datos, ya sea que el menú estuviera visible o no. Podrías implementar así:

   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 resaltado, invocar elemento resaltado) ya que el usuario no puede ver el resaltado. Sin embargo, puede devolver los valores si lo desea.

InvokeMenuItem

A continuación, proporcione un método InvokeMenuItem() que asigne posiciones enteras en la matriz de elementos devueltos por GetMenuItems() a las acciones. Para la matriz de elementos en el ejemplo tardis, use:

   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 desea que el sistema no haga nada). Este es el lugar para proporcionar funcionalidad de recuperación adicional más allá de lo que hay en el sistema: agregue un elemento en su menú, ejecútelo aquí cuando se invoque ese elemento de menú y devuelva NO_ACTION para que el sistema no haga nada más.

BuiltinAction contiene los siguientes valores:

  • NO_ACCIÓN . Hacer nada.
  • REINICIAR . Salga de la recuperación y reinicie el dispositivo normalmente.
  • APLICAR_EXT, APLICAR_CACHE, APLICAR_ADB_SIDELOAD . Instale un paquete de actualización desde varios lugares. Para obtener más información, consulte Carga local .
  • LIMPIAR_CACHÉ . Vuelva a formatear la partición de caché solamente. No se requiere confirmación ya que esto es relativamente inofensivo.
  • LIMPIAR_DATOS . Vuelva a formatear las particiones de datos de usuario y caché, también conocido como restablecimiento de datos 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 ha elegido restablecer los datos de fábrica desde el sistema principal). Este método se llama antes de que se borren los datos de usuario y las particiones de caché. Si su dispositivo almacena datos de usuario en cualquier lugar que no sean esas dos particiones, debe borrarlos aquí. Debe devolver 0 para indicar el éxito y otro valor para el fracaso, aunque actualmente se ignora el valor devuelto. Los datos de usuario y las particiones de caché se borran ya sea que devuelva el éxito o el fracaso.

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

Hacer dispositivo

Por último, incluye un texto estándar al final del archivo recovery_ui.cpp para la función make_device() que crea y devuelve una instancia de tu clase de dispositivo:

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

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

Después de completar el archivo recovery_ui.cpp, créelo y vincúlelo a la recuperación en su dispositivo. En Android.mk, cree 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, especifique su 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 IU de recuperación

La interfaz de usuario de recuperación consta de imágenes. Idealmente, los usuarios nunca interactúan con la interfaz de usuario: durante una actualización normal, el teléfono inicia la recuperación, llena la barra de progreso de la instalación y reinicia el nuevo sistema sin la intervención del usuario. En caso de un problema de actualización del sistema, la única acción que puede tomar el usuario es llamar al servicio de atención al cliente.

Una interfaz de solo imagen evita la necesidad de localización. Sin embargo, a partir de Android 5.0, la actualización puede mostrar una cadena de texto (por ejemplo, "Instalando actualización del sistema...") junto con la imagen. Para obtener más información, consulte Texto de recuperación localizado .

Android 5.0 y posterior

La interfaz de usuario de recuperación de Android 5.0 y versiones posteriores utiliza dos imágenes principales: la imagen de error y la animación de instalación .

imagen que se muestra durante el error ota

Figura 1. icono_error.png

imagen que se muestra durante la instalación ota

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 eso la Figura 2 aparece aplastada). Por ejemplo, para una animación de siete fotogramas de 200x200, cree una sola imagen de 200x1400 donde el primer fotograma sean las filas 0, 7, 14, 21, ...; el segundo cuadro son las filas 1, 8, 15, 22, ...; etc. La imagen combinada incluye un fragmento de texto que indica el número de cuadros de animación y el número de cuadros por segundo (FPS). La herramienta bootable/recovery/interlace-frames.py toma un conjunto de cuadros de entrada y los combina en la imagen compuesta necesaria utilizada por la recuperación.

Las imágenes predeterminadas están disponibles en diferentes densidades y se encuentran en bootable/recovery/res-$DENSITY/images (por ejemplo, bootable/recovery/res-hdpi/images ). Para utilizar una imagen estática durante la instalación, solo necesita proporcionar la imagen icon_installing.png y establecer el número de fotogramas en la animación en 0 (el icono de error no está animado; siempre es una imagen estática).

Android 4.x y anteriores

La interfaz de usuario 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 ota

Figura 3. icon_installing.png

imagen mostrada como primera superposición

Figura 4. icon-installing_overlay01.png

imagen mostrada como séptima superposición

Figura 5. icon_installing_overlay07.png

Durante la instalación, la visualización en pantalla se construye dibujando la imagen icon_installing.png y luego dibujando uno de los marcos superpuestos en la parte superior con el desplazamiento adecuado. Aquí, se superpone un cuadro rojo para resaltar dónde se coloca la superposición en la parte superior de la imagen base:

imagen compuesta de instalación más primera superposición

Figura 6. Instalación del cuadro de animación 1 (icon_installing.png + icon_installing_overlay01.png)

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

Figura 7. Instalación del cuadro de animación 7 (icon_installing.png + icon_installing_overlay07.png)

Los fotogramas subsiguientes se muestran dibujando solo la siguiente imagen superpuesta encima de lo que ya está allí; la imagen base no se vuelve a dibujar.

El número de fotogramas en la animación, la velocidad deseada y los desplazamientos x e y de la superposición en relación con la base se establecen mediante variables miembro de la clase ScreenRecoveryUI. Cuando use imágenes personalizadas en lugar de imágenes predeterminadas, anule el método Init() en su subclase para cambiar estos valores para sus imágenes personalizadas (para obtener detalles, consulte ScreenRecoveryUI ). El script bootable/recovery/make-overlay.py puede ayudar a convertir un conjunto de marcos de imagen al formato "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 utilizar una imagen estática durante la instalación, solo necesita proporcionar la imagen icon_installing.png y establecer el número de fotogramas en la animación en 0 (el icono 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 inicia la 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 muestra de cadenas de texto de recuperación:

imagen de texto de recuperación

Figura 8. Texto localizado para mensajes de recuperación

El texto de recuperación puede mostrar los siguientes mensajes:

  • Instalando actualización del sistema...
  • ¡Error!
  • Borrando... (al realizar un borrado de datos/restablecimiento de fábrica)
  • Sin comando (cuando un usuario inicia la recuperación manualmente)

La aplicación de Android en bootable/recovery/tools/recovery_l10n/ representa las localizaciones de un mensaje y crea la imagen compuesta. Para obtener detalles sobre el uso de esta aplicación, consulte los comentarios en bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java .

Cuando un usuario inicia la recuperación manualmente, es posible que la configuración regional no esté disponible y no se muestre ningún texto. No haga que los mensajes de texto sean críticos 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 principal (o animación). La barra de progreso se realiza combinando dos imágenes de entrada, que deben ser del mismo tamaño:

barra de progreso vacía

Figura 9. progreso_vacío.png

barra de progreso completa

Figura 10. progreso_llenar.png

El extremo izquierdo de la imagen de relleno se muestra junto al extremo derecho de la imagen vacía para hacer la barra de progreso. La posición del límite entre las dos imágenes se cambia para indicar el progreso. Por ejemplo, con los pares anteriores de imágenes de entrada, muestre:

barra de progreso al 1%

Figura 11. Barra de progreso al 1%>

barra de progreso al 10%

Figura 12. Barra de progreso al 10%

barra de progreso al 50%

Figura 13. Barra de progreso al 50%

Puede 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 enumerados anteriormente; cuando se encuentra un archivo en ese directorio, el sistema de compilación lo usa con preferencia a 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 para la recuperación y es un idioma de derecha a izquierda (RTL) (árabe, hebreo, etc.), la barra de progreso se llena de derecha a izquierda.

Dispositivos sin pantallas

No todos los dispositivos Android tienen pantallas. Si su dispositivo es un dispositivo sin periféricos o tiene una interfaz de solo audio, es posible que deba realizar una personalización más amplia de la IU de recuperación. En lugar de crear una subclase de ScreenRecoveryUI, crea una subclase de su clase principal RecoveryUI directamente.

RecoveryUI tiene métodos para manejar operaciones de interfaz de usuario de nivel inferior, como "alternar la pantalla", "actualizar la barra de progreso", "mostrar el menú", "cambiar la selección del menú", etc. Puede anularlos para proporcionar una interfaz adecuada para su dispositivo. Tal vez su dispositivo tenga LED donde puede usar diferentes colores o patrones de parpadeo para indicar el estado, o tal vez pueda reproducir audio. (Tal vez no desee admitir un menú o el modo de "visualización de texto" en absoluto; puede evitar acceder a ellos con las CheckKey() y HandleMenuKey() que nunca activan la visualización ni seleccionan un elemento del menú. En este caso , muchos de los métodos de RecoveryUI que debe proporcionar pueden ser solo stubs vacíos).

Consulte bootable/recovery/ui.h para la declaración de RecoveryUI para ver qué métodos debe admitir. RecoveryUI es abstracto: algunos métodos son puramente virtuales y deben ser proporcionados por subclases, pero contiene el código para procesar las entradas clave. También puede anular eso, si su dispositivo no tiene claves o si desea procesarlas de manera diferente.

actualizador

Puede usar un código específico del dispositivo en la instalación del paquete de actualización al proporcionar sus propias funciones de extensión a las que se puede llamar desde su secuencia de comandos de actualización. Aquí hay 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* , el número de argumentos entrantes y una matriz de punteros Expr* representan los argumentos. El valor devuelto 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);
    }

Sus argumentos no han sido evaluados en el momento en que se llama a su función; la lógica de su función determina cuáles de ellos se evalúan y cuántas veces. Por lo tanto, puede usar funciones de extensión para implementar sus propias estructuras de control. Call Evaluate() para evaluar un argumento Expr* y devolver un Value* . Si Evaluate() devuelve NULL, debe liberar cualquier recurso que tenga e inmediatamente devolver NULL (esto propaga abortos en la pila de edify). De lo contrario, usted asume la propiedad del Valor devuelto y es responsable de eventualmente llamar a FreeValue() en él.

Supongamos que la función necesita dos argumentos: una clave con valor de cadena y una imagen con valor de blob. Podrías leer argumentos como este:

   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 NULL y liberar argumentos previamente evaluados puede volverse tedioso para múltiples argumentos. La función ReadValueArgs() puede facilitar esto. En lugar del código anterior, podrías haber escrito esto:

   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 verificación de tipos, por lo que debe hacerlo aquí; es más conveniente hacerlo con una declaración if a costa de producir un mensaje de error algo menos específico cuando falla. Pero ReadValueArgs() maneja la evaluación de cada argumento y libera todos los argumentos evaluados previamente (además de establecer un mensaje de error útil) si alguna de las evaluaciones falla. Puede usar una función de conveniencia ReadValueVarArgs() para evaluar una cantidad variable de argumentos (devuelve una matriz de Value* ).

Después de evaluar los argumentos, haz 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 de retorno debe ser un objeto Value* ; la propiedad de este objeto pasará a la persona que llama. La persona que llama asume la propiedad de cualquier dato señalado por este Value* específicamente el miembro de datos.

En este caso, desea devolver un valor verdadero o falso para indicar el éxito. Recuerde la convención de que la cadena vacía es falsa y todas las demás cadenas son verdaderas . Debe malloc un objeto Value con una copia malloc'd de la cadena constante para regresar, ya que la persona que llama free() a ambos. ¡No olvides llamar a FreeValue() en los objetos que obtuviste al evaluar 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() envuelve una cadena en un nuevo objeto de valor. Use para escribir el código anterior de manera más sucinta:

   FreeValue(key);
    FreeValue(image);

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

Para enlazar funciones en el intérprete de edify, proporcione la función Register_ foo donde foo es el nombre de la biblioteca estática que contiene este código. Llame a RegisterFunction() para registrar cada función de extensión. Por convención, nombra dispositivo de funciones específicas del device . whatever lo que device . whatever para evitar conflictos con futuras funciones integradas añadidas.

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

Ahora puede configurar el archivo MAKE para crear una biblioteca estática con su código. (Este es el mismo archivo MAKE utilizado para personalizar la interfaz de usuario de recuperación en la sección anterior; su dispositivo puede tener 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 contenida en ella.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Finalmente, configure la compilación de recuperación para extraer su biblioteca. Agregue su biblioteca a TARGET_RECOVERY_UPDATER_LIBS (que puede contener varias bibliotecas; todas se registran). Si su código depende de otras bibliotecas estáticas que no son en sí mismas extensiones de Edify (es decir, no tienen una función Register_ libname ), puede enumerarlas en TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vincularlas al actualizador sin llamar a su (inexistente) función de registro. Por ejemplo, si el código específico de su dispositivo quisiera usar zlib para descomprimir datos, incluiría 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 +=

Los scripts de actualización en su paquete OTA ahora pueden llamar a su función como cualquier otra. Para reprogramar su dispositivo tardis, el script de actualización puede contener: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Esto utiliza la versión de argumento único 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 es hacer que las herramientas de generación de paquetes OTA conozcan los datos específicos de su dispositivo y emitan scripts de actualización que incluyen llamadas a sus funciones de extensión.

En primer lugar, haga que el sistema de compilación conozca un blob de datos específico del dispositivo. Asumiendo que su archivo de datos está en device/yoyodyne/tardis/tardis.dat , declare lo siguiente en el AndroidBoard.mk de su dispositivo:

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

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

También podría ponerlo en un Android.mk, pero luego debe estar protegido por una verificación de dispositivo, ya que todos los archivos Android.mk en el árbol se cargan sin importar qué dispositivo se esté construyendo. (Si su árbol incluye varios dispositivos, solo desea que se agregue el archivo tardis.dat al crear 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 llaman archivos de radio por razones históricas; es posible que no tengan nada que ver con la radio del dispositivo (si está presente). Son simplemente gotas opacas de datos que el sistema de compilación copia en los archivos de destino .zip utilizados por las herramientas de generación de OTA. Cuando realiza una compilación, tardis.dat se almacena en target-files.zip como RADIO/tardis.dat . Puede llamar add-radio-file varias veces para agregar tantos archivos como desee.

Módulo Python

Para ampliar las herramientas de lanzamiento, escriba 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 separada maneja el caso de generar un paquete OTA incremental. Para este ejemplo, suponga que necesita reprogramar el tardis solo cuando el archivo tardis.dat ha 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

Puede proporcionar las siguientes funciones en el módulo (implemente solo las que necesite).

FullOTA_Assertions()
Llamado cerca del inicio de la generación de una OTA completa. Este es un buen lugar para emitir aseveraciones sobre el estado actual del dispositivo. No emita comandos de script que realicen cambios en el dispositivo.
FullOTA_InstallBegin()
Se llama después de que hayan pasado todas las aserciones sobre el estado del dispositivo, pero antes de que se hayan realizado cambios. Puede 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 del script, después de que se hayan emitido los comandos del script para actualizar las particiones de arranque y del sistema. También puede emitir comandos adicionales para actualizaciones específicas del dispositivo.
IncrementalOTA_Assertions()
Similar a FullOTA_Assertions() pero llamado al generar un paquete de actualización incremental.
IncrementalOTA_VerifyBegin()
Se llama después de que hayan pasado todas las aserciones sobre el estado del dispositivo, pero antes de que se hayan realizado cambios. Puede 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 el script ha terminado de confirmar que los archivos que va a tocar tienen el contenido inicial esperado. En este punto no se ha cambiado nada en el dispositivo. También puede emitir código para verificaciones adicionales específicas del dispositivo.
IncrementalOTA_InstallBegin()
Se llama después de que se haya verificado que los archivos que se van a aplicar parches tienen el estado anterior esperado, pero antes de que se hayan realizado cambios. Puede emitir comandos para actualizaciones específicas del dispositivo que deben ejecutarse antes de que se cambie cualquier otra cosa en el dispositivo.
IncrementalOTA_InstallEnd()
Similar a su contraparte del paquete OTA completo, esto se llama al final de la generación del script, después de que se hayan emitido los comandos del script para actualizar las particiones de arranque y del sistema. También puede emitir comandos adicionales para actualizaciones específicas del dispositivo.

Nota: si el dispositivo pierde energía, la instalación OTA puede reiniciarse desde el principio. Esté preparado para hacer frente a los dispositivos en los que estos comandos ya se han ejecutado, total o parcialmente.

Pasar funciones a objetos de información

Pase funciones a un solo objeto de información que contiene varios elementos útiles:

  • info.input_zip . (Solo OTA completos) El objeto zipfile.ZipFile para los archivos de destino de entrada .zip.
  • info.fuente_zip . (Solo OTA incrementales) El objeto zipfile.ZipFile para los archivos de destino de origen .zip (la compilación ya en el dispositivo cuando se instala el paquete incremental).
  • info.objetivo_zip . (Solo OTA incrementales) El objeto zipfile.ZipFile para los archivos de destino de destino .zip (la compilación que el paquete incremental coloca en el dispositivo).
  • info.salida_zip . Paquete que se está creando; un objeto zipfile.ZipFile abierto para escritura. Use common.ZipWriteStr(info.output_zip, filename , data ) para agregar un archivo al paquete.
  • info.script . Objeto de secuencia de comandos al que puede agregar comandos. Llame info.script.AppendExtra( script_text ) para generar texto en el script. Asegúrese de que el texto de salida termine con un punto y coma para que no se ejecute en los comandos emitidos después.

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

Especificar la ubicación del módulo

Especifique la ubicación del script releasetools.py de su dispositivo en su archivo BoardConfig.mk:

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

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Si TARGET_RELEASETOOLS_EXTENSIONS no está configurado, el valor predeterminado es el directorio $(TARGET_DEVICE_DIR)/../common ( device/yoyodyne/common en este ejemplo). Es mejor definir explícitamente la ubicación del script releasetools.py. Al construir el dispositivo tardis, el script releasetools.py se incluye en el archivo .zip de los archivos de destino ( META/releasetools.py ).

Cuando ejecuta las herramientas de publicación (ya sea img_from_target_files u ota_from_target_files ), la secuencia de comandos releasetools.py en el .zip de los archivos de destino, si está presente, es preferible a la del árbol de fuentes de Android. También puede especificar explícitamente la ruta a las extensiones específicas del dispositivo con la opción -s (o --device_specific ), que tiene la máxima prioridad. Esto le permite corregir errores y realizar cambios en las extensiones de releasetools y aplicar esos cambios a archivos de destino antiguos.

Ahora, cuando ejecuta ota_from_target_files , selecciona automáticamente el módulo específico del dispositivo del archivo .zip de target_files y lo usa al generar 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

Alternativamente, puede especificar extensiones específicas del dispositivo cuando ejecuta 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 opciones, consulte los comentarios de ota_from_target_files en build/make/tools/releasetools/ota_from_target_files .

Carga lateral

La recuperación tiene un mecanismo de carga lateral para instalar manualmente un paquete de actualización sin descargarlo de forma inalámbrica por el sistema principal. La instalación de prueba es útil para depurar o realizar cambios en dispositivos en los que no se puede iniciar el sistema principal.

Históricamente, la instalación de prueba se ha realizado mediante la carga de paquetes desde la tarjeta SD del dispositivo; en el caso de un dispositivo que no arranca, el paquete puede colocarse en la tarjeta SD utilizando otra computadora y luego insertar la tarjeta SD en el dispositivo. Para acomodar dispositivos Android sin almacenamiento externo extraíble, la recuperación admite dos mecanismos adicionales para la carga lateral: cargar paquetes desde la partición de caché y cargarlos a través de USB usando adb.

Para invocar cada mecanismo de prueba, el método Device::InvokeMenuItem() de su dispositivo puede devolver los siguientes valores de BuiltinAction:

  • APLICAR_EXT . Cargue un paquete de actualización desde un almacenamiento externo (directorio /sdcard ). Su recovery.fstab debe definir el punto de montaje /sdcard . This is not usable on devices that emulate an SD card with a symlink to /data (or some similar mechanism). /data is typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in /sdcard and allows the user to select one.
  • APPLY_CACHE . Similar to loading a package from /sdcard except that the /cache directory (which is always available to recovery) is used instead. From the regular system, /cache is only writable by privileged users, and if the device isn't bootable then the /cache directory can't be written to at all (which makes this mechanism of limited utility).
  • APPLY_ADB_SIDELOAD . Allows user to send a package to the device via a USB cable and the adb development tool. When this mechanism is invoked, recovery starts up its own mini version of the adbd daemon to let adb on a connected host computer talk to it. This mini version supports only a single command: adb sideload filename . The named file is sent from the host machine to the device, which then verifies and installs it just as if it had been on local storage.

A few caveats:

  • Only USB transport is supported.
  • If your recovery runs adbd normally (usually true for userdebug and eng builds), that will be shut down while the device is in adb sideload mode and will be restarted when adb sideload has finished receiving a package. While in adb sideload mode, no adb commands other than sideload work ( logcat , reboot , push , pull , shell , etc. all fail).
  • You cannot exit adb sideload mode on the device. To abort, you can send /dev/null (or anything else that's not a valid package) as the package, and then the device will fail to verify it and stop the installation procedure. The RecoveryUI implementation's CheckKey() method will continue to be called for keypresses, so you can provide a key sequence that reboots the device and works in adb sideload mode.