Codice specifico del dispositivo

Il sistema di ripristino include diversi hook per l'inserimento di codice specifico del dispositivo in modo che gli aggiornamenti OTA possano aggiornare anche parti del dispositivo diverse dal sistema Android (ad esempio, la banda base o il processore radio).

Le sezioni e gli esempi seguenti personalizzano il dispositivo tardis prodotto dal fornitore yoyodyne .

Mappa delle partizioni

A partire da Android 2.3, la piattaforma supporta i dispositivi flash eMMc e il filesystem ext4 che viene eseguito su tali dispositivi. Supporta anche i dispositivi flash Memory Technology Device (MTD) e il filesystem yaffs2 delle versioni precedenti.

Il file della mappa delle partizioni è specificato da TARGET_RECOVERY_FSTAB; questo file viene utilizzato sia dal file binario di ripristino che dagli strumenti di creazione dei pacchetti. È possibile specificare il nome del file mappa in TARGET_RECOVERY_FSTAB in BoardConfig.mk.

Un file di mappatura delle partizioni di esempio potrebbe assomigliare a questo:

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

Ad eccezione di /sdcard , che è facoltativo, tutti i punti di montaggio in questo esempio devono essere definiti (i dispositivi possono anche aggiungere partizioni aggiuntive). Esistono cinque tipi di file system supportati:

yafs2
Un filesystem yaffs2 su un dispositivo flash MTD. "device" deve essere il nome della partizione MTD e deve apparire in /proc/mtd .
mtd
Una partizione MTD grezza, utilizzata per partizioni avviabili come avvio e ripristino. MTD non è effettivamente montato, ma il punto di montaggio viene utilizzato come chiave per individuare la partizione. "device" deve essere il nome della partizione MTD in /proc/mtd .
ext4
Un filesystem ext4 su un dispositivo flash eMMc. "device" deve essere il percorso del dispositivo a blocchi.
emmc
Un dispositivo a blocchi eMMc grezzo, utilizzato per partizioni avviabili come avvio e ripristino. Similmente al tipo mtd, eMMc non viene mai effettivamente montato, ma la stringa del punto di montaggio viene utilizzata per individuare il dispositivo nella tabella.
vfat
Un file system FAT su un dispositivo a blocchi, in genere per l'archiviazione esterna come una scheda SD. Il dispositivo è il dispositivo a blocchi; device2 è un secondo dispositivo a blocchi che il sistema tenta di montare se il montaggio del dispositivo primario fallisce (per compatibilità con le schede SD che possono o meno essere formattate con una tabella delle partizioni).

Tutte le partizioni devono essere montate nella directory root (cioè il valore del punto di montaggio deve iniziare con una barra e non avere altre barre). Questa restrizione si applica solo al montaggio dei filesystem in ripristino; il sistema principale è libero di montarli ovunque. Le directory /boot , /recovery e /misc dovrebbero essere di tipo raw (mtd o emmc), mentre le directory /system , /data , /cache e /sdcard (se disponibile) dovrebbero essere di tipo filesystem (yaffs2, ext4 o vgrasso).

A partire da Android 3.0, il file recovery.fstab ottiene un campo facoltativo aggiuntivo, options . Attualmente l'unica opzione definita è length , che consente di specificare esplicitamente la lunghezza della partizione. Questa lunghezza viene utilizzata quando si riformatta la partizione (ad esempio, per la partizione dei dati utente durante un'operazione di cancellazione dei dati/ripristino delle impostazioni di fabbrica o per la partizione di sistema durante l'installazione di un pacchetto OTA completo). Se il valore della lunghezza è negativo, la dimensione da formattare viene ottenuta aggiungendo il valore della lunghezza alla dimensione effettiva della partizione. Ad esempio, impostando "length=-16384" significa che gli ultimi 16k di quella partizione non verranno sovrascritti quando quella partizione verrà riformattata. Ciò supporta funzionalità come la crittografia della partizione dei dati utente (dove i metadati di crittografia sono archiviati alla fine della partizione che non deve essere sovrascritta).

Nota: i campi dispositivo2 e opzioni sono facoltativi e creano ambiguità nell'analisi. Se la voce nel quarto campo della riga inizia con un carattere '/', viene considerata una voce dispositivo2 ; se la voce non inizia con il carattere '/', è considerata un campo di opzioni .

Animazione di avvio

I produttori di dispositivi hanno la possibilità di personalizzare l'animazione mostrata all'avvio di un dispositivo Android. Per fare ciò, costruire un file .zip organizzato e posizionato secondo le specifiche in formato bootanimation .

Per i dispositivi Android Things , puoi caricare il file zippato nella console Android Things per includere le immagini nel prodotto selezionato.

Nota: queste immagini devono rispettare le linee guida del brand Android .

Interfaccia utente di ripristino

Per supportare dispositivi con hardware diverso disponibile (pulsanti fisici, LED, schermi, ecc.), è possibile personalizzare l'interfaccia di ripristino per visualizzare lo stato e accedere alle funzionalità nascoste gestite manualmente per ciascun dispositivo.

Il tuo obiettivo è creare una piccola libreria statica con un paio di oggetti C++ per fornire funzionalità specifiche del dispositivo. Il file bootable/recovery/default_device.cpp viene utilizzato per impostazione predefinita e rappresenta un buon punto di partenza da copiare quando si scrive una versione di questo file per il dispositivo.

Nota: qui potresti visualizzare il messaggio Nessun comando . Per attivare/disattivare il testo, tieni premuto il pulsante di accensione mentre premi il pulsante di aumento del volume. Se il tuo dispositivo non dispone di entrambi i pulsanti, premi a lungo un pulsante qualsiasi per attivare/disattivare il testo.

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

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

Funzioni di intestazione e voce

La classe Device richiede funzioni per restituire intestazioni ed elementi visualizzati nel menu di ripristino nascosto. Le intestazioni descrivono come utilizzare il menu (ovvero i controlli per modificare/selezionare la voce evidenziata).

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: le righe lunghe vengono troncate (non mandate a capo), quindi tieni presente la larghezza dello schermo del tuo dispositivo.

Personalizza CheckKey

Successivamente, definisci l'implementazione RecoveryUI del tuo dispositivo. Questo esempio presuppone che il dispositivo Tardis abbia uno schermo, quindi puoi ereditare dall'implementazione ScreenRecoveryUI integrata (vedi istruzioni per i dispositivi senza schermo ). L'unica funzione da personalizzare da ScreenRecoveryUI è CheckKey() , che esegue la gestione iniziale della chiave asincrona:

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

Costanti CHIAVE

Le costanti KEY_* sono definite in linux/input.h . CheckKey() viene chiamato indipendentemente da ciò che accade nel resto del ripristino: quando il menu è disattivato, quando è attivo, durante l'installazione del pacchetto, durante la cancellazione dei dati utente, ecc. Può restituire una delle quattro costanti:

  • ALTERNA . Attiva o disattiva la visualizzazione del menu e/o l'accesso testuale
  • RIAVVIARE . Riavviare immediatamente il dispositivo
  • IGNORA . Ignora questa pressione di tasto
  • ACCODARE . Accoda la pressione di questo tasto affinché venga consumata in modo sincrono (ovvero, dal sistema del menu di ripristino se il display è abilitato)

CheckKey() viene chiamato ogni volta che un evento keydown è seguito da un evento keyup per lo stesso tasto. (La sequenza di eventi A-giù B-giù B-su A-su comporta solo la chiamata di CheckKey(B) .) CheckKey() può chiamare IsKeyPressed() , per scoprire se altri tasti vengono tenuti premuti. (Nella sequenza di eventi chiave sopra, se CheckKey(B) avesse chiamato IsKeyPressed(A) avrebbe restituito true.)

CheckKey() può mantenere lo stato nella sua classe; questo può essere utile per rilevare sequenze di tasti. Questo esempio mostra una configurazione leggermente più complessa: il display viene attivato tenendo premuto il tasto di accensione e premendo il volume su, e il dispositivo può essere riavviato immediatamente premendo il pulsante di accensione cinque volte di seguito (senza altri tasti intermedi):

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

Quando utilizzi le tue immagini (icona di errore, animazione di installazione, barre di avanzamento) con ScreenRecoveryUI, puoi impostare la variabile animation_fps per controllare la velocità in fotogrammi al secondo (FPS) delle animazioni.

Nota: l'attuale script interlace-frames.py ti consente di memorizzare le informazioni animation_fps nell'immagine stessa. Nelle versioni precedenti di Android era necessario impostare manualmente animation_fps .

Per impostare la variabile animation_fps , sovrascrivi la funzione ScreenRecoveryUI::Init() nella tua sottoclasse. Impostare il valore, quindi chiamare la funzione parent Init() per completare l'inizializzazione. Il valore predefinito (20 FPS) corrisponde alle immagini di ripristino predefinite; quando si utilizzano queste immagini non è necessario fornire una funzione Init() . Per dettagli sulle immagini, consulta Immagini dell'interfaccia utente di ripristino .

Classe del dispositivo

Dopo aver ottenuto un'implementazione RecoveryUI, definisci la classe del dispositivo (sottoclasse dalla classe Device incorporata). Dovrebbe creare una singola istanza della classe dell'interfaccia utente e restituirla dalla funzione GetUI() :

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

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

    RecoveryUI* GetUI() { return ui; }

AvviaRecupero

Il metodo StartRecovery() viene chiamato all'inizio del ripristino, dopo che l'interfaccia utente è stata inizializzata e dopo che gli argomenti sono stati analizzati, ma prima che venga intrapresa qualsiasi azione. L'implementazione predefinita non fa nulla, quindi non è necessario fornirla nella tua sottoclasse se non hai nulla da fare:

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

Fornire e gestire il menu di ripristino

Il sistema chiama due metodi per ottenere l'elenco delle righe di intestazione e l'elenco degli elementi. In questa implementazione, restituisce gli array statici definiti nella parte superiore del file:

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

HandleMenuKey

Successivamente, fornisci una funzione HandleMenuKey() , che rileva la pressione di un tasto e la visibilità del menu corrente e decide quale azione intraprendere:

   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;
    }

Il metodo accetta un codice chiave (che è stato precedentemente elaborato e accodato dal metodo CheckKey() dell'oggetto UI) e lo stato corrente della visibilità del menu/registro di testo. Il valore restituito è un numero intero. Se il valore è 0 o superiore, viene preso come posizione di una voce di menu, che viene richiamata immediatamente (vedere il metodo InvokeMenuItem() di seguito). Altrimenti può essere una delle seguenti costanti predefinite:

  • kEvidenziaSu . Sposta l'evidenziazione del menu sulla voce precedente
  • kEvidenziaGiù . Sposta l'evidenziazione del menu sulla voce successiva
  • kInvokeItem . Richiama l'elemento attualmente evidenziato
  • kNoAzione . Non fare nulla premendo questo tasto

Come implicito nell'argomento visibile, HandleMenuKey() viene chiamato anche se il menu non è visibile. A differenza di CheckKey() , non viene chiamato mentre il ripristino sta eseguendo operazioni come la cancellazione dei dati o l'installazione di un pacchetto: viene chiamato solo quando il ripristino è inattivo e in attesa di input.

Meccanismi della trackball

Se il tuo dispositivo ha un meccanismo di input simile a una trackball (genera eventi di input con tipo EV_REL e codice REL_Y), il ripristino sintetizza le pressioni dei tasti KEY_UP e KEY_DOWN ogni volta che il dispositivo di input simile a trackball segnala un movimento sull'asse Y. Tutto quello che devi fare è mappare gli eventi KEY_UP e KEY_DOWN sulle azioni del menu. Questa mappatura non avviene per CheckKey() , quindi non è possibile utilizzare i movimenti della trackball come trigger per riavviare o alternare la visualizzazione.

Tasti modificatori

Per verificare la presenza di tasti tenuti premuti come modificatori, chiama il metodo IsKeyPressed() del tuo oggetto dell'interfaccia utente. Ad esempio, su alcuni dispositivi premendo Alt-W durante il ripristino si avvia la cancellazione dei dati indipendentemente dal fatto che il menu sia visibile o meno. Potresti implementare in questo modo:

   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: se visibile è false, non ha senso restituire i valori speciali che manipolano il menu (sposta l'evidenziazione, richiama l'elemento evidenziato) poiché l'utente non può vedere l'evidenziazione. Tuttavia, se lo si desidera, è possibile restituire i valori.

InvokeMenuItem

Successivamente, fornisci un metodo InvokeMenuItem() che associ le posizioni dei numeri interi nell'array di elementi restituiti da GetMenuItems() alle azioni. Per l'array di elementi nell'esempio tardis, utilizzare:

   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;
        }
    }

Questo metodo può restituire qualsiasi membro dell'enumerazione BuiltinAction per indicare al sistema di intraprendere quell'azione (o il membro NO_ACTION se vuoi che il sistema non faccia nulla). Questo è il posto dove fornire funzionalità di ripristino aggiuntive oltre a quelle presenti nel sistema: aggiungi una voce nel tuo menu, eseguila qui quando viene richiamata quella voce di menu e restituisci NO_ACTION in modo che il sistema non faccia nient'altro.

BuiltinAction contiene i seguenti valori:

  • NESSUNA AZIONE . Fare niente.
  • RIAVVIARE . Uscire dal ripristino e riavviare normalmente il dispositivo.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD . Installa un pacchetto di aggiornamento da varie posizioni. Per i dettagli, vedere Sideloading .
  • WIPE_CACHE . Riformattare solo la partizione della cache. Non è richiesta alcuna conferma poiché è relativamente innocuo.
  • WIPE_DATA . Riformattare le partizioni dei dati utente e della cache, noto anche come ripristino dei dati di fabbrica. All'utente viene chiesto di confermare questa azione prima di procedere.

L'ultimo metodo, WipeData() , è facoltativo e viene chiamato ogni volta che viene avviata un'operazione di cancellazione dei dati (dal ripristino tramite il menu o quando l'utente ha scelto di eseguire un ripristino dei dati di fabbrica dal sistema principale). Questo metodo viene chiamato prima che i dati dell'utente e le partizioni della cache vengano cancellati. Se il tuo dispositivo memorizza i dati dell'utente in un punto diverso da queste due partizioni, dovresti cancellarli qui. Dovresti restituire 0 per indicare il successo e un altro valore per il fallimento, sebbene attualmente il valore restituito venga ignorato. I dati utente e le partizioni della cache vengono cancellati sia in caso di esito positivo che negativo.

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

Crea dispositivo

Infine, includi alcuni boilerplate alla fine del file recovery_ui.cpp per la funzione make_device() che crea e restituisce un'istanza della classe Device:

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

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

Dopo aver completato il file recovery_ui.cpp, crealo e collegalo al ripristino sul tuo dispositivo. In Android.mk, crea una libreria statica che contenga solo questo file 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)

Quindi, nella configurazione della scheda per questo dispositivo, specifica la tua libreria statica come valore di TARGET_RECOVERY_UI_LIB.

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

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

Immagini dell'interfaccia utente di ripristino

L'interfaccia utente di ripristino è composta da immagini. Idealmente, gli utenti non interagiscono mai con l'interfaccia utente: durante un normale aggiornamento, il telefono si avvia in modalità di ripristino, riempie la barra di avanzamento dell'installazione e si riavvia nel nuovo sistema senza input da parte dell'utente. In caso di problemi con l'aggiornamento del sistema, l'unica azione che può essere intrapresa dall'utente è chiamare l'assistenza clienti.

Un'interfaccia di sole immagini elimina la necessità di localizzazione. Tuttavia, a partire da Android 5.0 l'aggiornamento può visualizzare una stringa di testo (ad esempio "Installazione dell'aggiornamento di sistema...") insieme all'immagine. Per maggiori dettagli, consulta Testo di ripristino localizzato .

Android 5.0 e versioni successive

L'interfaccia utente di ripristino di Android 5.0 e versioni successive utilizza due immagini principali: l'immagine dell'errore e l'animazione di installazione .

immagine mostrata durante l'errore OTA

Figura 1. icon_error.png

immagine mostrata durante l'installazione OTA

Figura 2. icon_installing.png

L'animazione di installazione è rappresentata come una singola immagine PNG con diversi fotogrammi dell'animazione interlacciati per riga (motivo per cui la Figura 2 appare schiacciata). Ad esempio, per un'animazione da sette fotogrammi 200x200, crea una singola immagine da 200x1400 in cui il primo fotogramma corrisponde alle righe 0, 7, 14, 21, ...; il secondo frame sono le righe 1, 8, 15, 22, ...; ecc. L'immagine combinata include una porzione di testo che indica il numero di fotogrammi di animazione e il numero di fotogrammi al secondo (FPS). Lo strumento bootable/recovery/interlace-frames.py prende una serie di fotogrammi di input e li combina nell'immagine composita necessaria utilizzata dal ripristino.

Le immagini predefinite sono disponibili in diverse densità e si trovano in bootable/recovery/res-$DENSITY/images (ad esempio, bootable/recovery/res-hdpi/images ). Per utilizzare un'immagine statica durante l'installazione, è sufficiente fornire l'immagine icon_installing.png e impostare il numero di fotogrammi nell'animazione su 0 (l'icona di errore non è animata; è sempre un'immagine statica).

Android 4.xe versioni precedenti

L'interfaccia utente di ripristino di Android 4.x e versioni precedenti utilizza l'immagine di errore (mostrata sopra) e l'animazione di installazione oltre a diverse immagini sovrapposte:

immagine mostrata durante l'installazione OTA

Figura 3. icon_installing.png

immagine mostrata come prima sovrapposizione

Figura 4. icon-installing_overlay01.png

immagine mostrata come settima sovrapposizione

Figura 5. icon_installing_overlay07.png

Durante l'installazione, la visualizzazione su schermo viene costruita disegnando l'immagine icon_installing.png, quindi disegnando uno dei fotogrammi sovrapposti su di essa con l'offset corretto. Qui, viene sovrapposto un riquadro rosso per evidenziare il punto in cui è posizionata la sovrapposizione sopra l'immagine di base:

immagine composita dell'installazione più il primo overlay

Figura 6. Installazione del fotogramma di animazione 1 (icon_installing.png + icon_installing_overlay01.png)

immagine composita dell'installazione più il settimo overlay

Figura 7. Installazione del fotogramma di animazione 7 (icon_installing.png + icon_installing_overlay07.png)

I fotogrammi successivi vengono visualizzati disegnando solo l'immagine sovrapposta successiva sopra ciò che è già presente; l'immagine di base non viene ridisegnata.

Il numero di fotogrammi nell'animazione, la velocità desiderata e gli offset x e y della sovrapposizione rispetto alla base sono impostati dalle variabili membro della classe ScreenRecoveryUI. Quando utilizzi immagini personalizzate invece di immagini predefinite, sovrascrivi il metodo Init() nella tua sottoclasse per modificare questi valori per le tue immagini personalizzate (per i dettagli, vedi ScreenRecoveryUI ). Lo script bootable/recovery/make-overlay.py può aiutare a convertire una serie di fotogrammi di immagine nel formato "immagine base + immagini sovrapposte" richiesto dal ripristino, incluso il calcolo degli offset necessari.

Le immagini predefinite si trovano in bootable/recovery/res/images . Per utilizzare un'immagine statica durante l'installazione, è sufficiente fornire l'immagine icon_installing.png e impostare il numero di fotogrammi nell'animazione su 0 (l'icona di errore non è animata; è sempre un'immagine statica).

Testo di ripristino localizzato

Android 5.x visualizza una stringa di testo (ad esempio, "Installazione dell'aggiornamento di sistema...") insieme all'immagine. Quando il sistema principale si avvia in modalità di ripristino, passa le impostazioni internazionali correnti dell'utente come opzione della riga di comando al ripristino. Per ogni messaggio da visualizzare, il ripristino include una seconda immagine composita con stringhe di testo pre-renderizzate per quel messaggio in ogni locale.

Immagine di esempio delle stringhe di testo di ripristino:

immagine del testo di ripristino

Figura 8. Testo localizzato per i messaggi di ripristino

Il testo di ripristino può visualizzare i seguenti messaggi:

  • Installazione dell'aggiornamento del sistema in corso...
  • Errore!
  • Cancellazione in corso... (quando si esegue la cancellazione dei dati/il ripristino delle impostazioni di fabbrica)
  • Nessun comando (quando un utente avvia manualmente il ripristino)

L'app Android in bootable/recovery/tools/recovery_l10n/ esegue il rendering delle localizzazioni di un messaggio e crea l'immagine composita. Per i dettagli sull'utilizzo di questa app, fare riferimento ai commenti in bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java .

Quando un utente avvia manualmente il ripristino, la locale potrebbe non essere disponibile e non viene visualizzato alcun testo. Non rendere i messaggi di testo fondamentali per il processo di recupero.

Nota: l'interfaccia nascosta che visualizza i messaggi di registro e consente all'utente di selezionare azioni dal menu è disponibile solo in inglese.

Barre di avanzamento

Le barre di avanzamento possono essere visualizzate sotto l'immagine principale (o l'animazione). La barra di avanzamento viene creata combinando due immagini di input, che devono avere la stessa dimensione:

barra di avanzamento vuota

Figura 9. progress_empty.png

barra di avanzamento completa

Figura 10. progress_fill.png

L'estremità sinistra dell'immagine di riempimento viene visualizzata accanto all'estremità destra dell'immagine vuota per creare la barra di avanzamento. La posizione del confine tra le due immagini viene modificata per indicare l'avanzamento. Ad esempio, con le coppie di immagini di input sopra, visualizzare:

barra di avanzamento all'1%

Figura 11. Barra di avanzamento all'1%>

barra di avanzamento al 10%

Figura 12. Barra di avanzamento al 10%

barra di avanzamento al 50%

Figura 13. Barra di avanzamento al 50%

Puoi fornire versioni specifiche del dispositivo di queste immagini inserendole in (in questo esempio) device/yoyodyne/tardis/recovery/res/images . I nomi dei file devono corrispondere a quelli sopra elencati; quando un file viene trovato in quella directory, il sistema di compilazione lo utilizza preferibilmente rispetto all'immagine predefinita corrispondente. Sono supportati solo PNG in formato RGB o RGBA con profondità colore a 8 bit.

Nota: in Android 5.x, se la lingua è nota al ripristino ed è una lingua da destra a sinistra (RTL) (arabo, ebraico e così via), la barra di avanzamento si riempie da destra a sinistra.

Dispositivi senza schermi

Non tutti i dispositivi Android hanno schermi. Se il tuo dispositivo è un dispositivo headless o dispone di un'interfaccia solo audio, potrebbe essere necessario eseguire una personalizzazione più estesa dell'interfaccia utente di ripristino. Invece di creare una sottoclasse di ScreenRecoveryUI, sottoclassare direttamente la sua classe genitore RecoveryUI.

RecoveryUI dispone di metodi per gestire operazioni dell'interfaccia utente di livello inferiore come "attiva/disattiva la visualizzazione", "aggiorna la barra di avanzamento", "mostra il menu", "modifica la selezione del menu" ecc. È possibile sovrascriverli per fornire un'interfaccia appropriata per il tuo dispositivo. Forse il tuo dispositivo è dotato di LED in cui puoi utilizzare diversi colori o schemi di lampeggiamento per indicare lo stato, o forse puoi riprodurre l'audio. (Forse non vuoi supportare affatto un menu o la modalità di "visualizzazione del testo"; puoi impedire l'accesso ad essi con le implementazioni CheckKey() e HandleMenuKey() che non attivano mai la visualizzazione o selezionano una voce di menu. In questo caso , molti dei metodi RecoveryUI che devi fornire possono essere semplicemente stub vuoti.)

Vedi bootable/recovery/ui.h per la dichiarazione di RecoveryUI per vedere quali metodi devi supportare. RecoveryUI è astratto (alcuni metodi sono puramente virtuali e devono essere forniti da sottoclassi) ma contiene il codice per eseguire l'elaborazione degli input chiave. Puoi sovrascriverlo anche se il tuo dispositivo non dispone di chiavi o desideri elaborarle in modo diverso.

Aggiornamento

È possibile utilizzare il codice specifico del dispositivo nell'installazione del pacchetto di aggiornamento fornendo le proprie funzioni di estensione che possono essere richiamate dallo script di aggiornamento. Ecco una funzione di esempio per il dispositivo Tardis:

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

#include "edify/expr.h"

Ogni funzione di estensione ha la stessa firma. Gli argomenti sono il nome con cui è stata chiamata la funzione, un cookie State* , il numero di argomenti in entrata e un array di puntatori Expr* che rappresentano gli argomenti. Il valore restituito è un Value* appena allocato.

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);
    }

I tuoi argomenti non sono stati valutati nel momento in cui viene chiamata la tua funzione: la logica della tua funzione determina quali di essi vengono valutati e quante volte. Pertanto è possibile utilizzare le funzioni di estensione per implementare le proprie strutture di controllo. Call Evaluate() per valutare un argomento Expr* , restituendo un Value* . Se Evaluate() restituisce NULL, dovresti liberare tutte le risorse che hai in mano e restituire immediatamente NULL (questo si propaga interrompe lo stack edify). Altrimenti, diventi proprietario del valore restituito e sei responsabile di chiamare eventualmente FreeValue() su di esso.

Supponiamo che la funzione necessiti di due argomenti: una chiave con valori di stringa e un'immagine con valori di BLOB. Potresti leggere argomenti come questo:

   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;
    }

Controllare NULL e liberare argomenti valutati in precedenza può diventare noioso per più argomenti. La funzione ReadValueArgs() può semplificare l'operazione. Invece del codice sopra, avresti potuto scrivere questo:

   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() non esegue il controllo del tipo, quindi devi farlo qui; è più conveniente farlo con un'istruzione if al costo di produrre un messaggio di errore un po' meno specifico quando fallisce. Ma ReadValueArgs() gestisce la valutazione di ciascun argomento e la liberazione di tutti gli argomenti precedentemente valutati (oltre a impostare un utile messaggio di errore) se una qualsiasi delle valutazioni fallisce. È possibile utilizzare una funzione comoda ReadValueVarArgs() per valutare un numero variabile di argomenti (restituisce un array di Value* ).

Dopo aver valutato gli argomenti, esegui il lavoro della funzione:

   // 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 ...

Il valore restituito deve essere un oggetto Value* ; la proprietà di questo oggetto passerà al chiamante. Il chiamante assume la proprietà di tutti i dati indicati da questo Value* , in particolare del membro dati.

In questo caso, vuoi restituire un valore vero o falso per indicare il successo. Ricorda la convenzione secondo cui la stringa vuota è falsa e tutte le altre stringhe sono vere . È necessario eseguire il malloc di un oggetto Value con una copia malloc della stringa costante da restituire, poiché il chiamante free() entrambi. Non dimenticare di chiamare FreeValue() sugli oggetti che hai ottenuto valutando i tuoi argomenti!

   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 comoda funzione StringValue() racchiude una stringa in un nuovo oggetto Value. Utilizzare per scrivere il codice sopra in modo più conciso:

   FreeValue(key);
    FreeValue(image);

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

Per agganciare le funzioni all'interprete edify, fornire la funzione Register_ foo dove foo è il nome della libreria statica contenente questo codice. Chiama RegisterFunction() per registrare ciascuna funzione di estensione. Per convenzione, si denominano le funzioni specifiche device . whatever per evitare conflitti con future funzioni integrate aggiunte.

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

Ora puoi configurare il makefile per creare una libreria statica con il tuo codice. (Si tratta dello stesso makefile utilizzato per personalizzare l'interfaccia utente di ripristino nella sezione precedente; il tuo dispositivo potrebbe avere entrambe le librerie statiche definite qui.)

device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

Il nome della libreria statica deve corrispondere al nome della funzione Register_ libname contenuta al suo interno.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Infine, configura la build di ripristino per inserire la tua libreria. Aggiungi la tua libreria a TARGET_RECOVERY_UPDATER_LIBS (che può contenere più librerie; vengono tutte registrate). Se il tuo codice dipende da altre librerie statiche che non sono esse stesse estensioni edify (ovvero, non hanno una funzione Register_ libname ), puoi elencarle in TARGET_RECOVERY_UPDATER_EXTRA_LIBS per collegarle a updater senza chiamare la loro (inesistente) funzione di registrazione. Ad esempio, se il codice specifico del tuo dispositivo volesse utilizzare zlib per decomprimere i dati, includeresti libz qui.

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 +=

Gli script di aggiornamento nel tuo pacchetto OTA ora possono chiamare la tua funzione come qualsiasi altra. Per riprogrammare il tuo dispositivo tardis, lo script di aggiornamento potrebbe contenere: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Utilizza la versione ad argomento singolo della funzione incorporata package_extract_file() , che restituisce il contenuto di un file estratto dal pacchetto di aggiornamento come BLOB per produrre il secondo argomento per la nuova funzione di estensione.

Generazione di pacchetti OTA

Il componente finale è far sì che gli strumenti di generazione del pacchetto OTA conoscano i dati specifici del tuo dispositivo ed emettano script di aggiornamento che includono chiamate alle funzioni di estensione.

Innanzitutto, fai in modo che il sistema di compilazione venga a conoscenza di un blob di dati specifico del dispositivo. Supponendo che il tuo file di dati sia in device/yoyodyne/tardis/tardis.dat , dichiara quanto segue nell'AndroidBoard.mk del tuo dispositivo:

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

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

Potresti anche inserirlo in un Android.mk, ma in questo caso deve essere protetto da un controllo del dispositivo, poiché tutti i file Android.mk nell'albero vengono caricati indipendentemente dal dispositivo che viene creato. (Se il tuo albero include più dispositivi, vuoi che venga aggiunto solo il file tardis.dat durante la creazione del 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

Questi sono chiamati file radio per ragioni storiche; potrebbero non avere nulla a che vedere con la radio del dispositivo (se presente). Sono semplicemente blocchi opachi di dati che il sistema di compilazione copia nei file .zip di destinazione utilizzati dagli strumenti di generazione OTA. Quando esegui una compilazione, tardis.dat viene archiviato nel target-files.zip come RADIO/tardis.dat . Puoi chiamare add-radio-file più volte per aggiungere tutti i file che desideri.

Modulo Python

Per estendere gli strumenti di rilascio, scrivi un modulo Python (deve essere chiamato releasetools.py) a cui gli strumenti possono richiamare se presente. Esempio:

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 funzione separata gestisce il caso di generazione di un pacchetto OTA incrementale. Per questo esempio, supponiamo di dover riprogrammare il tardis solo quando il file tardis.dat è cambiato tra due build.

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"));""")

Funzioni del modulo

Puoi fornire le seguenti funzioni nel modulo (implementa solo quelle che ti servono).

FullOTA_Assertions()
Chiamato vicino all'inizio della generazione di un OTA completo. Questo è un buon posto per rilasciare asserzioni sullo stato attuale del dispositivo. Non emettere comandi script che apportano modifiche al dispositivo.
FullOTA_InstallBegin()
Chiamato dopo che tutte le asserzioni sullo stato del dispositivo sono state superate ma prima che vengano apportate eventuali modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima che qualsiasi altra cosa sul dispositivo venga modificata.
FullOTA_InstallEnd()
Chiamato al termine della generazione dello script, dopo che sono stati emessi i comandi dello script per aggiornare le partizioni di avvio e di sistema. Puoi anche emettere comandi aggiuntivi per aggiornamenti specifici del dispositivo.
IncrementalOTA_Assertions()
Simile a FullOTA_Assertions() ma chiamato durante la generazione di un pacchetto di aggiornamento incrementale.
IncrementalOTA_VerifyBegin()
Chiamato dopo che tutte le asserzioni sullo stato del dispositivo sono state superate ma prima che vengano apportate modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima che qualsiasi altra cosa sul dispositivo venga modificata.
IncrementalOTA_VerifyEnd()
Chiamato al termine della fase di verifica, quando lo script ha finito di verificare che i file che andrà a toccare abbiano il contenuto iniziale previsto. A questo punto non è stato modificato nulla sul dispositivo. Puoi anche emettere codice per ulteriori verifiche specifiche del dispositivo.
IncrementalOTA_InstallBegin()
Chiamato dopo che è stato verificato che i file a cui applicare la patch presentano lo stato previsto prima ma prima che vengano apportate eventuali modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima che qualsiasi altra cosa sul dispositivo venga modificata.
IncrementalOTA_InstallEnd()
Similmente alla controparte del pacchetto OTA completo, questo viene richiamato al termine della generazione dello script, dopo che sono stati emessi i comandi dello script per aggiornare le partizioni di avvio e di sistema. Puoi anche emettere comandi aggiuntivi per aggiornamenti specifici del dispositivo.

Nota: se il dispositivo perde alimentazione, l'installazione OTA potrebbe riavviarsi dall'inizio. Preparati a gestire dispositivi su cui questi comandi sono già stati eseguiti, completamente o parzialmente.

Passa funzioni agli oggetti informazioni

Passa le funzioni a un singolo oggetto informativo che contiene vari elementi utili:

  • info.input_zip . (Solo OTA completi) L'oggetto zipfile.ZipFile per i file di destinazione di input .zip.
  • info.source_zip . (Solo OTA incrementali) L'oggetto zipfile.ZipFile per i file di destinazione di origine .zip (la build già presente sul dispositivo quando viene installato il pacchetto incrementale).
  • info.target_zip . (Solo OTA incrementali) L'oggetto zipfile.ZipFile per i file di destinazione .zip (la build che il pacchetto incrementale inserisce nel dispositivo).
  • info.output_zip . Pacchetto in fase di creazione; un oggetto zipfile.ZipFile aperto per la scrittura. Utilizzare common.ZipWriteStr(info.output_zip, filename , data ) per aggiungere un file al pacchetto.
  • info.script . Oggetto script a cui è possibile aggiungere comandi. Chiama info.script.AppendExtra( script_text ) per generare il testo nello script. Assicurati che il testo di output termini con un punto e virgola in modo che non venga eseguito nei comandi emessi successivamente.

Per dettagli sull'oggetto informazioni, fare riferimento alla documentazione di Python Software Foundation per gli archivi ZIP .

Specificare la posizione del modulo

Specifica la posizione dello script releasetools.py del tuo dispositivo nel file BoardConfig.mk:

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

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Se TARGET_RELEASETOOLS_EXTENSIONS non è impostato, il valore predefinito è la directory $(TARGET_DEVICE_DIR)/../common ( device/yoyodyne/common in questo esempio). È meglio definire esplicitamente la posizione dello script releasetools.py. Durante la creazione del dispositivo Tardis, lo script releasetools.py è incluso nel file .zip dei file di destinazione ( META/releasetools.py ).

Quando esegui gli strumenti di rilascio ( img_from_target_files o ota_from_target_files ), lo script releasetools.py nel file target-files .zip, se presente, è preferito rispetto a quello dell'albero dei sorgenti Android. Puoi anche specificare esplicitamente il percorso delle estensioni specifiche del dispositivo con l'opzione -s (o --device_specific ), che ha la massima priorità. Ciò consente di correggere errori e apportare modifiche nelle estensioni releasetools e applicare tali modifiche ai vecchi file di destinazione.

Ora, quando esegui ota_from_target_files , preleva automaticamente il modulo specifico del dispositivo dal file target_files .zip e lo utilizza durante la generazione di pacchetti 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

In alternativa, puoi specificare estensioni specifiche del dispositivo quando esegui 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: per un elenco completo delle opzioni, fare riferimento ai commenti ota_from_target_files in build/make/tools/releasetools/ota_from_target_files .

Meccanismo di caricamento laterale

Il ripristino dispone di un meccanismo di sideload per l'installazione manuale di un pacchetto di aggiornamento senza scaricarlo via etere dal sistema principale. Il sideload è utile per eseguire il debug o apportare modifiche sui dispositivi in ​​cui non è possibile avviare il sistema principale.

Storicamente, il sideloading veniva effettuato caricando i pacchetti dalla scheda SD del dispositivo; nel caso di un dispositivo che non si avvia, il pacchetto può essere inserito nella scheda SD utilizzando un altro computer e quindi la scheda SD inserita nel dispositivo. Per ospitare dispositivi Android senza memoria esterna rimovibile, il ripristino supporta due meccanismi aggiuntivi per il sideload: caricamento dei pacchetti dalla partizione della cache e caricamento tramite USB utilizzando adb.

Per richiamare ciascun meccanismo di sideload, il metodo Device::InvokeMenuItem() del tuo dispositivo può restituire i seguenti valori di BuiltinAction:

  • APPLICA_EST . Sideload un pacchetto di aggiornamento dalla directory di archiviazione esterna ( /sdcard ). Il tuo recupero.fstab deve definire il punto di montaggio /sdcard . Questo non è utilizzabile su dispositivi che emulano una scheda SD con un collegamento simbolico a /data (o un meccanismo simile). /data in genere non sono disponibili per il recupero perché possono essere crittografati. L'interfaccia utente di recupero visualizza un menu di file .zip in /sdcard e consente all'utente di selezionarne uno.
  • Applica_cache . Simile al caricamento di un pacchetto da /sdcard , tranne per il fatto che viene utilizzata la directory /cache (che è sempre disponibile per il recupero). Dal sistema normale, /cache è scrittabile solo da utenti privilegiati e se il dispositivo non è avviabile, la directory /cache non può essere scritta affatto (il che rende questo meccanismo di utilità limitata).
  • Applic_adb_sideload . Consente all'utente di inviare un pacchetto al dispositivo tramite un cavo USB e lo strumento di sviluppo ADB. Quando questo meccanismo viene invocato, il recupero avvia la sua mini versione del demone ADBD per consentire ad ADB su un computer host connesso a parlare con esso. Questa mini versione supporta solo un singolo comando: adb sideload filename . Il file denominato viene inviato dalla macchina host al dispositivo, che quindi verifica e lo installa come se fosse stato su archiviazione locale.

Qualche avvertimento:

  • È supportato solo il trasporto USB.
  • Se il recupero esegue normalmente ADBD (di solito vero per le build UserDebug ed ENG), ciò verrà chiuso mentre il dispositivo è in modalità ADB Sideload e verrà riavviato quando ADB Sideload ha finito di ricevere un pacchetto. Mentre in modalità Sideload ADB, nessun comando ADB diverso dal lavoro sideload ( logcat , reboot , push , pull , shell , ecc. Tutti falliscono).
  • Non è possibile uscire dalla modalità Sideload ADB sul dispositivo. Per interrompere, è possibile inviare /dev/null (o qualsiasi altra cosa che non sia un pacchetto valido) come pacchetto, quindi il dispositivo non lo verificherà e interromperà la procedura di installazione. Il metodo CheckKey() di RecoveryUI Implementation continuerà a essere chiamato per i tasti, quindi è possibile fornire una sequenza chiave che riavvia il dispositivo e funziona in modalità ADB Sideload.