Codice specifico per il 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 es. la baseband o il processore radio).

Le seguenti sezioni ed esempi 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 file system ext4 che viene eseguito su questi dispositivi. Supporta anche i dispositivi flash Memory Technology Device (MTD) e il file system yaffs2 delle versioni precedenti.

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

Un file di mappatura delle partizioni di esempio potrebbe avere il seguente aspetto:

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:

yaffs2
Un file system yaffs2 su un dispositivo flash MTD. "device" deve essere il nome della partizione MTD e deve essere visualizzato in /proc/mtd.
mtd
Una partizione MTD non elaborata, utilizzata per partizioni avviabili come boot e recovery. 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 file system ext4 su un dispositivo flash eMMc. "device" deve essere il percorso del dispositivo a blocchi.
emmc
Un dispositivo a blocchi eMMC non elaborato, utilizzato per partizioni avviabili come boot e recovery. Simile al tipo mtd, eMMc non viene mai 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, ad esempio 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 principale non riesce (per la compatibilità con le schede SD che potrebbero o meno essere formattate con una tabella delle partizioni).

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

A partire da Android 3.0, il file recovery.fstab acquisisce un campo facoltativo aggiuntivo, options. Al momento l'unica opzione definita è length , che ti consente di specificare in modo esplicito la lunghezza della partizione. Questa lunghezza viene utilizzata durante la riformattazione della partizione (ad es. per la partizione userdata durante un'operazione di cancellazione dei dati/ripristino dei dati 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, l'impostazione "length=-16384" indica che gli ultimi 16.000 byte della partizione non verranno sovrascritti quando la partizione viene riformattata. Ciò supporta funzionalità come la crittografia della partizione userdata (in cui i metadati di crittografia sono archiviati alla fine della partizione che non deve essere sovrascritta).

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

Animazione di avvio

I produttori di dispositivi hanno la possibilità di personalizzare l'animazione mostrata all'avvio di un dispositivo Android. A tale scopo, crea un file .zip organizzato e posizionato in base alle specifiche del formato bootanimation.

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

Nota:queste immagini devono rispettare le linee guida per il brand Android. Per le linee guida per il brand, fai riferimento alla sezione Android del Partner Marketing Hub.

UI di ripristino

Per supportare dispositivi con hardware disponibile diverso (pulsanti fisici, LED, schermi e così via), puoi personalizzare l'interfaccia di ripristino per visualizzare lo stato e accedere alle funzionalità nascoste azionate manualmente per ogni dispositivo.

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

Nota:qui potresti visualizzare il messaggio Nessun comando. Per attivare/disattivare il testo, tieni premuto il tasto di accensione mentre premi il tasto Alza il volume. Se i tuoi dispositivi non hanno 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 articolo

La classe Device richiede funzioni per restituire intestazioni ed elementi visualizzati nel menu di ripristino nascosto. Le intestazioni descrivono come utilizzare il menu (ad es. i controlli per modificare/selezionare l'elemento evidenziato).

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

Personalizza CheckKey

Dopodiché, definisci l'implementazione di RecoveryUI del dispositivo. Questo esempio presuppone che il dispositivo tardis abbia uno schermo, quindi puoi ereditare dall'implementazione ScreenRecoveryUI integrata (vedi le istruzioni per i dispositivi senza schermo). L'unica funzione da personalizzare da ScreenRecoveryUI è CheckKey(), che gestisce inizialmente le chiavi in modo asincrono:

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

Costanti KEY

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 e così via. Può restituire una delle quattro costanti:

  • ATTIVA/DISATTIVA. Attiva o disattiva la visualizzazione del menu e/o del log di testo
  • RIAVVIA. Riavvia immediatamente il dispositivo
  • IGNORA. Ignora questa pressione di tasti
  • ENQUEUE. Metti in coda questa pressione dei tasti da utilizzare in modo sincrono (ovvero dal sistema di menu di ripristino se il display è abilitato)

CheckKey() viene chiamato ogni volta che un evento key-down è seguito da un evento key-up per la stessa chiave. (La sequenza di eventi A-down B-down B-up A-up comporta solo la chiamata di CheckKey(B).) CheckKey() può chiamare IsKeyPressed() per scoprire se altri tasti sono premuti. (Nella sequenza di eventi chiave riportata sopra, se CheckKey(B) avesse chiamato IsKeyPressed(A), avrebbe restituito true.)

CheckKey() può mantenere lo stato nella sua classe; ciò 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 tasto del volume su e il dispositivo può essere riavviato immediatamente premendo il tasto 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:lo script interlace-frames.py attuale consente di memorizzare le informazioni animation_fps nell'immagine stessa. Nelle versioni precedenti di Android era necessario impostare animation_fps manualmente.

Per impostare la variabile animation_fps, esegui l'override della funzione ScreenRecoveryUI::Init() nella sottoclasse. Imposta il valore, quindi chiama la funzione parent Init() per completare l'inizializzazione. Il valore predefinito (20 FPS) corrisponde alle immagini di ripristino predefinite; quando utilizzi queste immagini non devi fornire una funzione Init(). Per informazioni dettagliate sulle immagini, vedi Immagini dell'interfaccia utente di ripristino.

Classe del dispositivo

Dopo aver implementato RecoveryUI, definisci la classe del dispositivo (sottoclasse della classe Device integrata). Deve creare una singola istanza della classe UI e restituirla dalla funzione GetUI():

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

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

    RecoveryUI* GetUI() { return ui; }

StartRecovery

Il metodo StartRecovery() viene chiamato all'inizio del recupero, dopo l'inizializzazione dell'UI e l'analisi degli argomenti, ma prima che venga intrapresa qualsiasi azione. L'implementazione predefinita non fa nulla, quindi non è necessario fornirla nella 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 accetta la pressione di un tasto e la visibilità attuale del menu 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 (precedentemente elaborato e messo in coda dal metodo CheckKey() dell'oggetto UI) e lo stato attuale della visibilità del menu/registro di testo. Il valore restituito è un numero intero. Se il valore è 0 o superiore, viene considerato come la posizione di una voce di menu, che viene richiamata immediatamente (vedi il metodo InvokeMenuItem() di seguito). In caso contrario, può essere una delle seguenti costanti predefinite:

  • kHighlightUp. Sposta l'evidenziazione del menu sull'elemento precedente
  • kHighlightDown. Sposta l'evidenziazione del menu sull'elemento successivo
  • kInvokeItem. Richiama l'elemento attualmente evidenziato
  • kNoAction. Non fare nulla con questa pressione dei tasti

Come suggerito dall'argomento visibile, HandleMenuKey() viene chiamato anche se il menu non è visibile. A differenza di CheckKey(), non viene chiamato mentre il ripristino esegue un'operazione come la cancellazione dei dati o l'installazione di un pacchetto. Viene chiamato solo quando il ripristino è inattivo e in attesa di input.

Meccanismi di trackball

Se il tuo dispositivo ha un meccanismo di input simile a una trackball (genera eventi di input di 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 una trackball segnala un movimento sull'asse Y. Tutto ciò che devi fare è mappare gli eventi KEY_UP e KEY_DOWN sulle azioni del menu. Questa mappatura non avviene per CheckKey(), quindi non puoi utilizzare i movimenti della trackball come trigger per il riavvio o l'attivazione/disattivazione del display.

Tasti di modifica

Per verificare che i tasti vengano tenuti premuti come modificatori, chiama il metodo IsKeyPressed() del tuo oggetto UI. Ad esempio, su alcuni dispositivi la pressione di Alt+W nel ripristino avvia una cancellazione dei dati indipendentemente dalla visibilità del menu. Puoi 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 visible è false, non ha senso restituire i valori speciali che manipolano il menu (spostano l'evidenziazione, richiamano l'elemento evidenziato) poiché l'utente non può vedere l'evidenziazione. Tuttavia, se vuoi, puoi ripristinare i valori.

InvokeMenuItem

Successivamente, fornisci un metodo InvokeMenuItem() che mappa le posizioni intere nell'array di elementi restituiti da GetMenuItems() alle azioni. Per l'array di elementi nell'esempio di tardis, utilizza:

   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 eseguire l'azione (o il membro NO_ACTION se vuoi che il sistema non faccia nulla). Questo è il punto in cui fornire funzionalità di recupero aggiuntive oltre a quelle presenti nel sistema: aggiungi un elemento nel menu, eseguilo qui quando viene richiamato l'elemento di menu e restituisci NO_ACTION in modo che il sistema non faccia altro.

BuiltinAction contiene i seguenti valori:

  • NO_ACTION. Non fare niente.
  • RIAVVIA. Esci dal ripristino e riavvia normalmente il dispositivo.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Installare un pacchetto di aggiornamento da varie posizioni. Per maggiori dettagli, vedi Caricamento laterale.
  • WIPE_CACHE. Formatta solo la partizione della cache. Nessuna conferma richiesta in quanto si tratta di un'azione relativamente innocua.
  • WIPE_DATA. Riformattare le partizioni userdata e cache, operazione nota anche come ripristino 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 le partizioni dei dati utente e della cache vengano cancellate. Se il dispositivo memorizza dati utente in una posizione diversa da queste due partizioni, devi cancellarli qui. Devi restituire 0 per indicare la riuscita e un altro valore per l'errore, anche se attualmente il valore restituito viene ignorato. Le partizioni dei dati utente e della cache vengono cancellate indipendentemente dal fatto che venga restituito un esito positivo o negativo.

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

Marca del dispositivo

Infine, includi un testo standard 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, compilalo e collegalo al ripristino sul 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 della UI di ripristino

L'interfaccia utente di ripristino è costituita da immagini. Idealmente, gli utenti non interagiscono mai con la UI: Durante un aggiornamento normale, lo smartphone si avvia in modalità di ripristino, riempie la barra di avanzamento dell'installazione e si avvia di nuovo nel nuovo sistema senza input da parte dell'utente. In caso di problemi di aggiornamento del sistema, l'unica azione che l'utente può intraprendere è chiamare l'assistenza clienti.

Un'interfaccia solo con immagini elimina la necessità di localizzazione. Tuttavia, a partire da Android 5.0, l'aggiornamento può visualizzare una stringa di testo (ad es. "Installazione dell'aggiornamento di sistema…") insieme all'immagine. Per maggiori dettagli, vedi Testo di recupero 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 errore e l'animazione installazione.

immagine mostrata durante l&#39;errore OTA

Figura 1. icon_error.png

immagine mostrata durante l&#39;installazione OTA

Figura 2. icon_installing.png

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

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

Android 4.x e versioni precedenti

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

immagine mostrata durante l&#39;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 sullo schermo viene creata disegnando l'immagine icon_installing.png e poi uno dei frame di overlay sopra, con l'offset corretto. Qui, una casella rossa è sovrapposta per evidenziare il punto in cui l'overlay viene posizionato sopra l'immagine di base:

immagine composita di
installazione più primo overlay

Figura 6. Installazione del frame dell'animazione 1 (icon_installing.png + icon_installing_overlay01.png)

immagine composita di
installazione più settimo overlay

Figura 7. Installazione del frame dell'animazione 7 (icon_installing.png + icon_installing_overlay07.png)

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

Il numero di frame 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 anziché immagini predefinite, esegui l'override del metodo Init() nella sottoclasse per modificare questi valori per le immagini personalizzate (per maggiori dettagli, vedi ScreenRecoveryUI). Lo script bootable/recovery/make-overlay.py può aiutarti a convertire un insieme di frame di immagini nel formato "immagine di base + immagini di overlay" necessario per il recupero, incluso il calcolo degli offset necessari.

Le immagini predefinite si trovano in bootable/recovery/res/images. Per utilizzare un'immagine statica durante l'installazione, devi fornire solo 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 recupero localizzato

Android 5.x mostra una stringa di testo (ad es. "Installazione dell'aggiornamento di sistema in corso…") 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 per il ripristino. Per ogni messaggio da visualizzare, il recupero include una seconda immagine composita con stringhe di testo pre-renderizzate per quel messaggio in ogni lingua.

Immagine di esempio delle stringhe di testo di recupero:

immagine del testo di recupero

Figura 8. Testo localizzato per i messaggi di recupero

Il messaggio di recupero può visualizzare i seguenti messaggi:

  • Installazione dell'aggiornamento di sistema in corso…
  • Errore!
  • Cancellazione in corso… (durante la cancellazione dei dati/il ripristino dei dati di fabbrica)
  • Nessun comando (quando un utente esegue l'avvio in modalità di ripristino manualmente)

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

Quando un utente esegue l'avvio in modalità di ripristino manualmente, le impostazioni internazionali potrebbero non essere disponibili e non viene visualizzato alcun testo. Non rendere i messaggi critici per la procedura di recupero.

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

Barre di avanzamento

Le barre di avanzamento possono essere visualizzate sotto l'immagine (o l'animazione) principale. La barra di avanzamento è creata combinando due immagini di input, che devono avere le stesse dimensioni:

barra di avanzamento vuota

Figura 9. progress_empty.png

barra di avanzamento completa

Figura 10. progress_fill.png

L'estremità sinistra dell'immagine piena viene visualizzata accanto all'estremità destra dell'immagine vuota per formare 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 riportate sopra, visualizza:

barra di avanzamento all&#39;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 per dispositivo di queste immagini inserendole in (in questo esempio) device/yoyodyne/tardis/recovery/res/images . I nomi file devono corrispondere a quelli elencati sopra. Quando viene trovato un file in questa directory, il sistema di compilazione lo utilizza preferibilmente all'immagine predefinita corrispondente. Sono supportati solo i PNG in formato RGB o RGBA con profondità di colore a 8 bit.

Nota:in Android 5.x, se le impostazioni internazionali sono note al ripristino e sono in una lingua con scrittura da destra a sinistra (arabo, ebraico e così via), la barra di avanzamento si riempie da destra a sinistra.

Dispositivi senza schermo

Non tutti i dispositivi Android hanno schermi. Se il tuo dispositivo è un elettrodomestico headless o ha un'interfaccia solo audio, potrebbe essere necessario personalizzare in modo più approfondito la UI di ripristino. Invece di creare una sottoclasse di ScreenRecoveryUI, crea una sottoclasse direttamente della classe padre RecoveryUI.

RecoveryUI dispone di metodi per la gestione di operazioni dell'interfaccia utente di livello inferiore, come "attivare/disattivare la visualizzazione", "aggiornare la barra di avanzamento", "mostrare il menu", "modificare la selezione del menu" e così via. Puoi eseguire l'override di questi metodi per fornire un'interfaccia appropriata per il tuo dispositivo. Forse il tuo dispositivo ha dei LED che puoi usare con diversi colori o sequenze di lampeggio per indicare lo stato, oppure puoi riprodurre audio. Forse non vuoi supportare un menu o la modalità "visualizzazione testo "; puoi impedirne l'accesso con implementazioni di 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 solo stub vuoti.)

Consulta bootable/recovery/ui.h per la dichiarazione di RecoveryUI per vedere quali metodi devi supportare. RecoveryUI è astratta: alcuni metodi sono virtuali puri e devono essere forniti dalle sottoclassi, ma contiene il codice per l'elaborazione degli input della tastiera. Puoi anche ignorare questa impostazione se il tuo dispositivo non ha tasti o se vuoi elaborarli in modo diverso.

Google Updater

Puoi utilizzare codice specifico per il dispositivo nell'installazione del pacchetto di aggiornamento fornendo le tue funzioni di estensione che possono essere chiamate dall'interno dello 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 al momento della chiamata della funzione. La logica della funzione determina quali vengono valutati e quante volte. Pertanto, puoi utilizzare le funzioni di estensione per implementare le tue strutture di controllo. Call Evaluate() per valutare un argomento Expr* , restituendo un valore Value*. Se Evaluate() restituisce NULL, devi liberare le risorse che stai utilizzando e restituire immediatamente NULL (in questo modo le interruzioni vengono propagate verso l'alto nello stack edify). In caso contrario, diventi proprietario del valore restituito e sei responsabile di chiamare alla fine FreeValue().

Supponiamo che la funzione richieda due argomenti: una chiave con valore stringa e un'immagine con valore 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;
    }

Il controllo di NULL e la liberazione degli argomenti valutati in precedenza possono diventare noiosi per più argomenti. La funzione ReadValueArgs() può semplificare questa operazione. Invece del codice precedente, 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 dei tipi, quindi devi farlo qui. È più comodo farlo con un'istruzione if a costo di produrre un messaggio di errore un po' meno specifico in caso di errore. Tuttavia, ReadValueArgs() gestisce la valutazione di ogni argomento e la liberazione di tutti gli argomenti valutati in precedenza (oltre a impostare un messaggio di errore utile) se una delle valutazioni non va a buon fine. Puoi utilizzare una funzione di convenienza ReadValueVarArgs() per valutare un numero variabile di argomenti (restituisce un array di Value*).

Dopo aver valutato gli argomenti, esegui l'operazione 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 verrà trasferita al chiamante. Il chiamante acquisisce la proprietà di tutti i dati a cui fa riferimento questo Value*, in particolare il membro dati.

In questo caso, vuoi restituire un valore true o false per indicare la riuscita dell'operazione. Ricorda la convenzione secondo cui la stringa vuota è false e tutte le altre stringhe sono true. Devi allocare un oggetto Value con una copia allocata 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 funzione di convenienza StringValue() racchiude una stringa in un nuovo oggetto Value. Utilizza per scrivere il codice precedente in modo più conciso:

   FreeValue(key);
    FreeValue(image);

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

Per collegare le funzioni all'interprete edify, fornisci la funzione Register_foo dove foo è il nome della libreria statica contenente questo codice. Chiama RegisterFunction() per registrare ogni funzione di estensione. Per convenzione, assegna un nome alle funzioni specifiche del dispositivo device.whatever per evitare conflitti con le 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 la UI 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 del recupero per importare la 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 estensioni edify (ad es. non hanno una funzione Register_libname), puoi elencarle in TARGET_RECOVERY_UPDATER_EXTRA_LIBS per collegarle all'updater senza chiamare la loro funzione di registrazione (inesistente). Ad esempio, se il codice specifico del 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 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 a un solo argomento della funzione integrata package_extract_file(), che restituisce i contenuti di un file estratto dal pacchetto di aggiornamento come blob per produrre il secondo argomento della nuova funzione di estensione.

Generazione del pacchetto OTA

L'ultimo componente consiste nel fare in modo che gli strumenti di generazione dei pacchetti OTA conoscano i dati specifici del dispositivo ed emettano script di aggiornamento che includano chiamate alle funzioni di estensione.

Per prima cosa, fai in modo che il sistema di compilazione riconosca un blob di dati specifico del dispositivo. Supponendo che il file di dati si trovi in device/yoyodyne/tardis/tardis.dat, dichiara quanto segue in AndroidBoard.mk del dispositivo:

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

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

Puoi anche inserirlo in un file 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 in fase di compilazione. (Se l'albero include più dispositivi, vuoi che il file tardis.dat venga aggiunto solo quando crei il 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 file vengono chiamati file radio per motivi storici e potrebbero non avere nulla a che fare con la radio del dispositivo (se presente). Sono semplicemente blob di dati opachi che il sistema di compilazione copia nel file .zip target-files utilizzato dagli strumenti di generazione OTA. Quando esegui una build, tardis.dat viene memorizzato in target-files.zip come RADIO/tardis.dat. Puoi chiamare add-radio-file più volte per aggiungere tutti i file che vuoi.

Modulo Python

Per estendere gli strumenti di rilascio, scrivi un modulo Python (deve essere denominato releasetools.py) che gli strumenti possono chiamare 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 della generazione di un pacchetto OTA incrementale. Per questo esempio, supponiamo che tu debba riprogrammare il TARDIS solo quando il file tardis.dat è stato modificato 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 all'inizio della generazione di un aggiornamento OTA completo. Questo è un buon punto per emettere asserzioni sullo stato attuale del dispositivo. Non emettere comandi di 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 modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima di qualsiasi altra modifica sul dispositivo.
FullOTA_InstallEnd()
Chiamato alla fine della generazione dello script, dopo l'emissione dei 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 viene 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 di qualsiasi altra operazione sul dispositivo.
IncrementalOTA_VerifyEnd()
Chiamato al termine della fase di verifica, quando lo script ha terminato di confermare che i file che toccherà hanno i contenuti iniziali previsti. A questo punto, non è stato modificato nulla sul dispositivo. Puoi anche generare codice per ulteriori verifiche specifiche per il dispositivo.
IncrementalOTA_InstallBegin()
Chiamato dopo che i file da applicare sono stati verificati come aventi lo stato before previsto, ma prima che siano state apportate modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima di qualsiasi altra modifica sul dispositivo.
IncrementalOTA_InstallEnd()
Simile alla sua controparte del pacchetto OTA completo, viene chiamato alla fine della generazione dello script, dopo l'emissione dei 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 si spegne, l'installazione OTA potrebbe riavviarsi dall'inizio. Preparati a gestire i dispositivi su cui questi comandi sono già stati eseguiti, completamente o parzialmente.

Passare funzioni agli oggetti informativi

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

  • info.input_zip. (Solo OTA complete) L'oggetto zipfile.ZipFile per il file ZIP dei file di destinazione di input.
  • info.source_zip. (Solo OTA incrementali) L'oggetto zipfile.ZipFile per il file .zip target-files di origine (la build già presente sul dispositivo quando viene installato il pacchetto incrementale).
  • info.target_zip. (Solo OTA incrementali) L'oggetto zipfile.ZipFile per il file .zip target (la build del pacchetto incrementale inserita sul dispositivo).
  • info.output_zip. Pacchetto in fase di creazione; un oggetto zipfile.ZipFile aperto per la scrittura. Utilizza common.ZipWriteStr(info.output_zip, filename, data) per aggiungere un file al pacchetto.
  • info.script. Oggetto script a cui puoi aggiungere comandi. Call info.script.AppendExtra(script_text) to output text into the script. Assicurati che il testo di output termini con un punto e virgola in modo che non si imbatta nei comandi emessi successivamente.

Per informazioni dettagliate sull'oggetto info, consulta la documentazione della Python Software Foundation per gli archivi ZIP.

Specificare la posizione del modulo

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

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

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Se TARGET_RELEASETOOLS_EXTENSIONS non è impostato, per impostazione predefinita viene utilizzata la directory $(TARGET_DEVICE_DIR)/../common (device/yoyodyne/common in questo esempio). È consigliabile definire in modo esplicito la posizione dello script releasetools.py. Quando viene creato il dispositivo tardis, lo script releasetools.py è incluso nel file ZIP target-files (META/releasetools.py ).

Quando esegui gli strumenti di rilascio (img_from_target_files o ota_from_target_files), lo script releasetools.py nel file .zip target-files, se presente, ha la precedenza su quello dell'albero dei sorgenti di Android. Puoi anche specificare esplicitamente il percorso delle estensioni specifiche del dispositivo con l'opzione -s (o --device_specific), che ha la massima priorità. In questo modo puoi correggere gli errori e apportare modifiche nelle estensioni releasetools e applicarle ai vecchi file di destinazione.

Ora, quando esegui ota_from_target_files, il modulo specifico per il dispositivo viene selezionato automaticamente dal file .zip target_files e utilizzato durante la generazione dei 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, consulta i commenti ota_from_target_files in build/make/tools/releasetools/ota_from_target_files.

Meccanismo di sideload

Il ripristino ha un meccanismo di sideloading per installare manualmente un pacchetto di aggiornamento senza scaricarlo via OTA dal sistema principale. Il sideload è utile per il debug o per apportare modifiche sui dispositivi in cui non è possibile avviare il sistema principale.

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

Per richiamare ogni meccanismo di caricamento laterale, il metodo Device::InvokeMenuItem() del dispositivo può restituire i seguenti valori di BuiltinAction:

  • APPLY_EXT. Esegui il sideload di un pacchetto di aggiornamento da una memoria esterna (directory /sdcard). Il file recovery.fstab deve definire il punto di montaggio /sdcard . Questa non è utilizzabile sui dispositivi che emulano una scheda SD con un collegamento simbolico a /data (o un meccanismo simile). /data in genere non è disponibile per il recupero perché potrebbe essere criptato. L'interfaccia utente di recupero mostra un menu di file .zip in /sdcard e consente all'utente di selezionarne uno.
  • APPLY_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 è scrivibile solo da utenti con privilegi. Se il dispositivo non è avviabile, non è possibile scrivere nella directory /cache (il che rende questo meccanismo di utilità limitata).
  • APPLY_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 richiamato, il ripristino avvia una propria mini versione del daemon adbd per consentire ad adb su un computer host connesso di comunicare con esso. Questa mini versione supporta un solo comando: adb sideload filename. Il file denominato viene inviato dalla macchina host al dispositivo, che lo verifica e lo installa come se fosse stato nello spazio di archiviazione locale.

Alcune avvertenze:

  • È supportato solo il trasporto USB.
  • Se il ripristino esegue adbd normalmente (in genere vero per le build userdebug e eng), questo verrà arrestato mentre il dispositivo è in modalità di caricamento laterale adb e verrà riavviato al termine della ricezione di un pacchetto da parte di adb sideload. In modalità di caricamento laterale ADB, nessun comando ADB diverso da sideload funziona ( logcat, reboot, push, pull, shell e così via non funzionano).
  • Non puoi uscire dalla modalità di caricamento laterale adb sul dispositivo. Per interrompere l'operazione, puoi inviare /dev/null (o qualsiasi altro elemento che non sia un pacchetto valido) come pacchetto e il dispositivo non riuscirà a verificarlo e interromperà la procedura di installazione. Il metodo CheckKey() dell'implementazione di RecoveryUI continuerà a essere chiamato per le pressioni dei tasti, in modo da poter fornire una sequenza di tasti che riavvii il dispositivo e funzioni in modalità di caricamento laterale adb.