Código específico del dispositivo

El sistema de recuperación incluye varios enlaces para insertar código específico del dispositivo para que las actualizaciones OTA también puedan actualizar partes del dispositivo además del 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 partición

A partir de Android 2.3, la plataforma admite dispositivos flash eMMc y el sistema de archivos ext4 que se ejecuta en esos dispositivos. También es compatible con dispositivos flash Memory Technology Device (MTD) y el sistema de archivos yaffs2 de versiones anteriores.

El archivo de mapa de partición lo especifica TARGET_RECOVERY_FSTAB; Este archivo es utilizado tanto por el binario de recuperación como por 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 verse así:

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

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 formato, utilizada para particiones de arranque como arranque y recuperación. En realidad, MTD no está montado, pero el punto de montaje se utiliza como clave para localizar 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. Al igual que el tipo mtd, eMMc nunca se monta realmente, pero la cadena del punto de montaje se utiliza para ubicar el dispositivo en la tabla.
vfat
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; dispositivo2 es un segundo dispositivo de bloque que el sistema intenta montar si falla el montaje del dispositivo principal (por 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 y no tener otras barras). Esta restricción se aplica sólo 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án disponibles) deben ser tipos de sistema de archivos (yaffs2, ext4 o vfat).

A partir de Android 3.0, el archivo recovery.fstab obtiene un campo opcional adicional, opciones . Actualmente, la única opción definida es length , que le permite especificar explícitamente la longitud de la partición. Esta longitud se utiliza al reformatear la partición (por ejemplo, para la partición de datos del 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, entonces 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 vuelva a formatear esa partición. Esto admite funciones como el cifrado de la partición de datos del usuario (donde los metadatos de cifrado se almacenan al final de la partición y no deben sobrescribirse).

Nota: Los campos dispositivo2 y opciones 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 el 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 incluir las imágenes 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 C++ para proporcionar la funcionalidad específica del dispositivo. El archivo bootable/recovery/default_device.cpp se utiliza de forma predeterminada y constituye un buen punto de partida para copiar al escribir una versión de este archivo para su dispositivo.

Nota: Es posible que vea un mensaje que diga No hay comando aquí. Para alternar texto, mantenga presionado el botón de encendido mientras presiona el botón para subir el volumen. Si sus dispositivos no tienen ambos botones, mantenga presionado cualquier botón para alternar el texto.

device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

Funciones de encabezado y elemento

La clase Dispositivo 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 se truncan (no se ajustan), así que tenga en cuenta el ancho de la pantalla de su dispositivo.

Personalizar clave de verificación

A continuación, defina la implementación de RecoveryUI de su dispositivo. Este ejemplo supone que el dispositivo tardis tiene una pantalla, por lo que puede heredar de la implementación integrada de ScreenRecoveryUI (consulte las instrucciones para dispositivos sin pantalla ). La única función que se puede 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_* están definidas en linux/input.h . Se llama 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 eliminación de datos del usuario, etc. Puede devolver una de cuatro constantes:

  • CAMBIAR . Activar o desactivar la visualización del menú y/o el registro de texto
  • REINICIAR . Reinicie inmediatamente el dispositivo
  • IGNORAR . Ignora esta pulsación de tecla
  • EN COLA . Ponga en cola esta pulsación de tecla para que se consuma de forma sincrónica (es decir, mediante el sistema de menú de recuperación si la pantalla está habilitada)

CheckKey() se llama cada vez que a un evento de pulsación de tecla le sigue un evento de pulsación de tecla para la misma clave. (La secuencia de eventos A-down B-down B-up A-up solo da como resultado que se llame a CheckKey(B) .) CheckKey() puede llamar a IsKeyPressed() para averiguar si se están manteniendo presionadas otras teclas. (En la secuencia anterior de eventos clave, si CheckKey(B) llamara a IsKeyPressed(A) habría devuelto verdadero).

CheckKey() puede mantener el estado en su clase; Esto puede resultar útil para detectar secuencias de claves. 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;
    }
};

Interfaz de usuario de recuperación de pantalla

Cuando utilice 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 fotogramas por segundo (FPS) de las animaciones.

Nota: El script interlace-frames.py actual 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 , anule la función ScreenRecoveryUI::Init() en su subclase. Establezca el valor y 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 utilizar estas imágenes, no es necesario proporcionar una función Init() . Para obtener detalles sobre las imágenes, consulte Imágenes de la interfaz de usuario de recuperación .

Clase de dispositivo

Después de tener una implementación de RecoveryUI, defina su clase de dispositivo (subclasificada de la clase de dispositivo incorporada). Debería crear una única instancia de su clase UI 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

El método StartRecovery() se llama al inicio de la recuperación, después de que se haya inicializado la interfaz de usuario y después de que se hayan analizado los argumentos, pero antes de que se haya realizado cualquier acción. La implementación predeterminada no hace nada, por lo que no es necesario que proporciones esto en tu subclase si no tienes nada que hacer:

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

Menú de suministro y gestión 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, devuelve las matrices estáticas definidas en la parte superior del archivo:

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

ManejarMenúClave

A continuación, proporcione una función HandleMenuKey() , que requiere presionar una 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 menú/registro de texto. El valor de retorno 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() a continuación). De lo contrario, puede ser una de las siguientes constantes predefinidas:

  • kResaltar . Mover el resaltado del menú al elemento anterior
  • kResaltarAbajo . Mover el resaltado del menú al siguiente elemento
  • kInvocarItem . Invocar el elemento actualmente resaltado
  • kSinAcción . No hacer nada con esta pulsación de tecla

Como lo implica el argumento visible, se llama 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; se llama solo cuando la recuperación está inactiva y esperando entrada.

Mecanismos de trackball

Si su dispositivo tiene un mecanismo de entrada similar a una bola de seguimiento (genera eventos de entrada con tipo EV_REL y código REL_Y), la recuperación sintetiza las pulsaciones de teclas KEY_UP y KEY_DOWN cada vez que el dispositivo de entrada similar a una bola de seguimiento informa movimiento en el eje Y. Todo lo que necesitas hacer es asignar eventos KEY_UP y KEY_DOWN a acciones del menú. Esta asignación no ocurre para CheckKey() , por lo que no puede usar los movimientos de la trackball 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 UI. Por ejemplo, en algunos dispositivos, presionar Alt-W en 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.

Invocar elemento de menú

A continuación, proporcione un método InvokeMenuItem() que asigne posiciones enteras en la matriz de elementos devueltos por GetMenuItems() a acciones. Para la matriz de elementos en el ejemplo de 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 del menú y devuelva NO_ACTION para que el sistema no haga nada más.

BuiltinAction contiene los siguientes valores:

  • SIN_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 Descarga lateral .
  • WIPE_CACHE . Vuelva a formatear sólo la partición de caché. No se requiere confirmación ya que esto es relativamente inofensivo.
  • BORRAR INFORMACIÓN . Vuelva a formatear los datos de usuario y las particiones de caché, también conocido como restablecimiento de datos de fábrica. Se 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 borrado de datos (ya sea desde la recuperación a través del menú o cuando el usuario ha elegido realizar un restablecimiento de datos de fábrica desde el sistema principal). Este método se llama antes de que se borren los datos del usuario y las particiones de caché. Si su dispositivo almacena datos de usuario en otro lugar que no sean esas dos particiones, debe borrarlos aquí. Debe devolver 0 para indicar éxito y otro valor para error, aunque actualmente el valor devuelto se ignora. Los datos del usuario y las particiones de caché se borran ya sea que el resultado sea exitoso o fallido.

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

Hacer dispositivo

Finalmente, incluya algo de texto estándar al final del archivo recovery_ui.cpp para la función make_device() que crea y devuelve una instancia de su 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, constrúyalo 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 la interfaz de usuario 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 se inicia en recuperación, llena la barra de progreso de la instalación y se reinicia en el nuevo sistema sin intervención del usuario. En caso de un problema de 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 sólo imágenes elimina la necesidad de localización. Sin embargo, a partir de Android 5.0, la actualización puede mostrar una cadena de texto (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 mostrada durante el error ota

Figura 1. icon_error.png

imagen mostrada durante la instalación de ota

Figura 2. icon_installing.png

La animación de instalación se representa como una única imagen PNG con diferentes cuadros de la animación entrelazados por fila (razón por la cual la Figura 2 aparece aplastada). Por ejemplo, para una animación de siete cuadros de 200x200, cree una única imagen de 200x1400 donde el primer cuadro 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 fotogramas de animación y el número 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 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 usar una imagen estática durante la instalación, solo necesita proporcionar la imagen icon_installing.png y establecer el número de cuadros en la animación en 0 (el ícono 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 utiliza la imagen de error (que se muestra arriba) y la animación de instalación , además de varias imágenes superpuestas:

imagen mostrada durante la instalación de 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 encima de ella con el desplazamiento adecuado. Aquí, se superpone un cuadro rojo para resaltar dónde se coloca la superposición encima 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 posteriores se muestran dibujando sólo la siguiente imagen superpuesta encima de la que ya está allí; la imagen base no se vuelve a dibujar.

El número de fotogramas de 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 utilice 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 más detalles, consulte ScreenRecoveryUI ). El script bootable/recovery/make-overlay.py puede ayudar a convertir un conjunto de marcos de imagen al formato de "imagen base + imágenes superpuestas" que necesita la recuperación, incluido el cálculo de las compensaciones necesarias.

Las imágenes predeterminadas se encuentran en bootable/recovery/res/images . Para usar una imagen estática durante la instalación, solo necesita proporcionar la imagen icon_installing.png y establecer el número de cuadros 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 (por ejemplo, "Instalando actualización del sistema...") junto con la imagen. Cuando el sistema principal arranca en recuperación, pasa la configuración regional actual del usuario como una opción de línea de comandos para 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 ubicación.

Imagen de muestra de cadenas de texto de recuperación:

imagen del 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 mensajes de registro y permite al usuario seleccionar acciones del menú está disponible solo 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 forma combinando dos imágenes de entrada, las cuales deben ser del mismo tamaño:

barra de progreso vacía

Figura 9. progreso_empty.png

barra de progreso completa

Figura 10. progreso_relleno.png

El extremo izquierdo de la imagen de relleno se muestra junto al extremo derecho de la imagen vacía para formar la barra de progreso. La posición del límite entre las dos imágenes cambia para indicar el progreso. Por ejemplo, con los pares de imágenes de entrada anteriores, 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 archivos 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 profundidad de color de 8 bits.

Nota: En Android 5.x, si se sabe que la configuración regional se recupera 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 pantalla. Si su dispositivo es un dispositivo sin cabeza o tiene una interfaz de solo audio, es posible que deba realizar una personalización más exhaustiva de la interfaz de usuario de recuperación. En lugar de crear una subclase de ScreenRecoveryUI, cree una subclase de su clase principal RecoveryUI directamente.

RecoveryUI tiene métodos para manejar operaciones de UI 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 tu dispositivo. Tal vez su dispositivo tenga LED donde pueda usar diferentes colores o patrones de parpadeo para indicar el estado, o tal vez pueda reproducir audio. (Quizás no desee admitir un menú o el modo de "visualización de texto" en absoluto; puede evitar el acceso a ellos con las implementaciones 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 simplemente resguardos vacíos).

Consulte bootable/recovery/ui.h para ver la declaración de RecoveryUI y 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 puedes anular eso si tu dispositivo no tiene claves o quieres procesarlas de manera diferente.

Actualizador

Puede utilizar código específico del dispositivo en la instalación del paquete de actualización proporcionando sus propias funciones de extensión que se pueden llamar desde su script 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* que representan los argumentos. El valor de retorno 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 utilizar 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, debes liberar todos los recursos que tengas e inmediatamente devolver NULL (esto propaga los abortos en la pila de Edify). De lo contrario, usted toma posesión del Valor devuelto y es responsable de llamar FreeValue() en el futuro.

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 evaluados previamente puede resultar 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() se encarga de evaluar cada argumento y liberar todos los argumentos evaluados previamente (además de establecer un mensaje de error útil) si alguna de las evaluaciones falla. Puede utilizar una función de conveniencia ReadValueVarArgs() para evaluar un número 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 toma posesión 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 asignar mal un objeto Valor con una copia mal asignada de la cadena constante para devolver, ya que la persona que llama free() ambos. ¡No olvides llamar 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 Value. Úselo para escribir el código anterior de manera más sucinta:

   FreeValue(key);
    FreeValue(image);

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

Para conectar funciones al 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 RegisterFunction() para registrar cada función de extensión. Por convención, nombre las funciones específicas device . whatever para evitar conflictos con futuras funciones integradas agregadas.

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 usado 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 incorporar 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 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 función de registro (inexistente). 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 un solo argumento de la función incorporada 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 incluyan llamadas a las funciones de su extensión.

Primero, haga que el sistema de compilación conozca una masa de datos específica del dispositivo. Suponiendo 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 puedes 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 agregar 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 denominan 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 manchas opacas de datos que el sistema de compilación copia en los archivos de destino .zip utilizados por las herramientas de generación 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 de pitón

Para ampliar las herramientas de lanzamiento, escriba un módulo de Python (debe llamarse releasetools.py) al que las herramientas puedan llamar, si están presentes. 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 haya cambiado entre dos compilaciones.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Funciones del módulo

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 afirmaciones 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 afirmaciones 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 se llama al generar un paquete de actualización incremental.
IncrementalOTA_VerifyBegin()
Se llama después de que hayan pasado todas las afirmaciones 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 parchear tienen el estado esperado antes , 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 se queda sin energía, la instalación OTA puede reiniciarse desde el principio. Esté preparado para hacer frente a dispositivos en los que estos comandos ya se han ejecutado, total o parcialmente.

Pasar funciones a objetos de información.

Pase funciones a un único objeto de información que contenga varios elementos útiles:

  • información.input_zip . (Solo OTA completas) El objeto zipfile.ZipFile para los archivos de destino de entrada .zip.
  • info.source_zip . (Solo OTA incrementales) El objeto zipfile.ZipFile para los archivos de destino de origen .zip (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 los archivos de destino .zip de destino (la compilación que el paquete incremental coloca en el dispositivo).
  • información.output_zip . Paquete en proceso de creación; un objeto zipfile.ZipFile abierto para escritura. Utilice common.ZipWriteStr(info.output_zip, nombre de archivo , datos ) 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 posteriormente.

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 archivos de destino ( META/releasetools.py ).

Cuando ejecuta las herramientas de lanzamiento (ya sea img_from_target_files u ota_from_target_files ), se prefiere el script releasetools.py en el archivo .zip de archivos de destino, si está presente, al del árbol fuente 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 , automáticamente selecciona el módulo específico del dispositivo del archivo target_files .zip 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 .

Mecanismo de carga lateral

La recuperación tiene un mecanismo de descarga para instalar manualmente un paquete de actualización sin descargarlo de forma inalámbrica mediante el sistema principal. La descarga es útil para depurar o realizar cambios en dispositivos donde no se puede iniciar el sistema principal.

Históricamente, la descarga se ha realizado cargando paquetes desde la tarjeta SD del dispositivo; en el caso de un dispositivo que no arranca, el paquete se puede colocar en la tarjeta SD usando 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 descarga: cargar paquetes desde la partición de caché y cargarlos a través de USB usando adb.

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

  • APLICAR_EXT . Cargue un paquete de actualización desde el almacenamiento externo (directorio /sdcard ). Su recuperación. FSTAB debe definir el punto de montaje /sdcard . Esto no se puede usar en dispositivos que emulan una tarjeta SD con un enlace simbólico a /data (o algún mecanismo similar). /data generalmente no están disponibles para la recuperación porque puede estar encriptada. La interfaz de usuario de recuperación muestra un menú de archivos .zip en /sdcard y permite al usuario seleccionar uno.
  • Aplicar_cache . Similar a la carga de un paquete de /sdcard , excepto que el directorio /cache (que siempre está disponible para la recuperación) se usa en su lugar. Desde el sistema regular, los usuarios privilegiados solo son escritos, y si el dispositivo no se puede iniciar, el directorio /cache /cache se puede escribir en absoluto (lo que hace que este mecanismo de utilidad limitada).
  • Aplicar_adb_sideload . Permite al usuario enviar un paquete al dispositivo a través de un cable USB y la herramienta de desarrollo ADB. Cuando se invoca este mecanismo, la recuperación inicia su propia versión mini del Daemon ADBD para dejar que ADB en una computadora host conectada le hable. Esta versión mini admite solo un comando: adb sideload filename . El archivo nombrado se envía desde la máquina host al dispositivo, que luego verifica e instala como si hubiera estado en el almacenamiento local.

Algunas advertencias:

  • Solo es compatible con el transporte USB.
  • Si su recuperación ejecuta ADBD normalmente (generalmente es cierto para UserDebug y Eng Build), eso se cerrará mientras el dispositivo esté en modo ADB Sideload y se reiniciará cuando ADB Sideload haya terminado de recibir un paquete. Mientras está en el modo ADB Sideload, no hay comandos ADB que no sean el trabajo sideload ( logcat , reboot , push , pull , shell , etc. Todos fallan).
  • No puede salir del modo ADB Sideload en el dispositivo. Para abortar, puede enviar /dev/null (o cualquier otra cosa que no sea un paquete válido) como el paquete, y luego el dispositivo no lo verificará y detendrá el procedimiento de instalación. El método CheckKey() de la implementación de recuperación de recuperación continuará siendo llamado para KeyPresses, por lo que puede proporcionar una secuencia clave que reinicie el dispositivo y funcione en el modo ADB Sideload.