Codice specifico del dispositivo

Il sistema di recupero 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 banda di base o il processore radio).

Le sezioni e gli esempi seguenti consentono di personalizzare 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 eseguito su questi dispositivi. Supporta inoltre i dispositivi flash MTD (Memory Technology Device) e il filesystem yaffs2 delle release precedenti.

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

Un file di mappa della partizione 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 filesystem 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 le partizioni avviabili come avvio e ripristino. L'unità MTD non è effettivamente montata, 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 di blocco.
eMMC
Un dispositivo di blocco eMMC non elaborato, utilizzato per le partizioni avviabili come avvio e ripristino. Analogamente 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 di blocco, in genere per l'archiviazione esterna come una scheda SD. Il dispositivo è il dispositivo di blocco; device2 è un secondo dispositivo di blocco che il sistema tenta di montare se il montaggio del dispositivo principale non va a buon fine (per la compatibilità con le schede SD che possono o meno essere formattate con una tabella delle partizioni).

Tutte le partizioni devono essere montate nella home directory (ovvero il valore del punto di montaggio deve iniziare con una barra e non deve contenere altre barre). Questa limitazione si applica solo al montaggio dei filesystem in fase di recupero; il sistema principale è libero di montarli ovunque. Le directory /boot, /recovery e /misc devono essere di tipo raw (mtd o emmc), mentre le directory /system, /data, /cache e /sdcard (se disponibili) devono essere di tipo filesystem (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 consente di specificare esplicitamente la lunghezza della partizione. Questa lunghezza viene utilizzata durante la riformattazione della partizione (ad esempio per la partizione userdata durante un'operazione di reset 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, le dimensioni da formattare vengono prese aggiungendo il valore della lunghezza alle dimensioni effettive della partizione. Ad esempio, l'impostazione "length=-16384" indica che gli ultimi 16 KB della partizione non verranno sovrascritti quando la partizione viene riformattata. Sono supportate funzionalità come la crittografia della partizione userdata (dove i metadati di crittografia vengono memorizzati 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 un carattere "/", viene considerata una voce device2. Se la voce non inizia con un carattere "/", viene considerata un campo options.

Animazione di avvio

I produttori di dispositivi hanno la possibilità di personalizzare l'animazione mostrata durante l'avvio di un dispositivo Android. A tale scopo, crea un file ZIP organizzato e posizionato in base alle specifiche in 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 del brand Android. Per le linee guida del brand, consulta la sezione Android del Partner Marketing Hub.

Interfaccia utente di ripristino

Per supportare dispositivi con hardware diverso (pulsanti fisici, LED, schermi e così via), Puoi 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 la funzionalità specifica del dispositivo. Il file bootable/recovery/default_device.cpp viene utilizzato per impostazione predefinita ed è un buon punto di partenza per la copia quando scrivi una versione di questo file per il tuo dispositivo.

Nota: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 recupero 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 rientrano), quindi tieni presente la larghezza dello schermo del tuo dispositivo.

Personalizzare CheckKey

Successivamente, 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 la gestione iniziale delle chiavi asincrone:

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 cosa stia succedendo nel resto del recupero: quando il menu è disattivato, quando è attivo, durante l'installazione del pacchetto, durante l'eliminazione dei dati utente e così via. Può restituire una delle quattro costanti:

  • TOGGLE. Attiva o disattiva la visualizzazione del menu e/o del log di testo
  • RIAVVIA. Riavvia immediatamente il dispositivo
  • IGNORE. Ignora questa pressione del tasto
  • ENQUEUE. Inserisci questa pressione del tasto in coda per il consumo sincrono (ad es. dal sistema di menu di recupero 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-giù B-giù B-su A-su comporta solo la chiamata di CheckKey(B). CheckKey() può chiamare IsKeyPressed() per scoprire se sono stati premuti altri tasti. 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; questo può essere utile per rilevare sequenze di chiavi. Questo esempio mostra una configurazione leggermente più complessa: il display viene attivato e disattivato 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 ti consente di memorizzare le informazioni animation_fps nell'immagine stessa. Nelle versioni precedenti di Android era necessario impostare animation_fps autonomamente.

Per impostare la variabile animation_fps, sostituisci la 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 è necessario fornire una funzione Init(). Per informazioni dettagliate sulle immagini, consulta Immagini dell'interfaccia utente di recupero.

Classe del dispositivo

Dopo aver creato un'implementazione di RecoveryUI, definisci la classe del dispositivo (sottoclasse della classe Device integrata). Dovrebbe creare una singola istanza della tua 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'interfaccia utente e l'analisi degli argomenti, ma prima che venga intrapresa un'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 ....
    }

Menu di recupero e gestione dell'offerta

Il sistema chiama due metodi per ottenere l'elenco delle righe di intestazione e l'elenco degli articoli. 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

Poi, fornisci una funzione HandleMenuKey(), che prende un tasto premuto e la visibilità del menu corrente e decide quale azione eseguire:

   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 inserito in coda dal metodo CheckKey() dell'oggetto UI) e lo stato corrente della visibilità del log del menu/del testo. Il valore restituito è un numero intero. Se il valore è uguale o superiore a 0, viene considerata la posizione di un elemento del menu, che viene richiamato 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 del tasto

Come deducibile dall'argomento visibile, HandleMenuKey() viene chiamato anche se il menu non è visibile. A differenza di CheckKey(), non viene chiamato mentre il recupero sta eseguendo un'operazione come l'eliminazione dei dati o l'installazione di un pacchetto, ma solo quando il recupero è inattivo e in attesa di input.

Meccanismi trackball

Se il dispositivo ha un meccanismo di immissione simile a una trackball (genera eventi di immissione di tipo EV_REL e codice REL_Y), il recupero sintetizza le pressioni dei tasti KEY_UP e KEY_DOWN ogni volta che il dispositivo di immissione simile a una trackball segnala un movimento sull'asse Y. Devi solo mappare gli eventi KEY_UP e KEY_DOWN alle azioni del menu. Questa mappatura non si verifica per CheckKey(), pertanto non puoi utilizzare i movimenti del trackball come attivatori per il riavvio o l'attivazione/la disattivazione del display.

Tasti di modifica

Per verificare se i tasti sono premuti come modificatori, chiama il metodo IsKeyPressed() del tuo oggetto UI. Ad esempio, su alcuni dispositivi, la pressione di Alt-W in modalità di ripristino avviava la cancellazione dei dati, indipendentemente dal fatto che il menu fosse visibile o meno. Puoi implementare come segue:

   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 (sposta l'evidenziazione, richiama l'elemento evidenziato) poiché l'utente non può vedere l'evidenziazione. Tuttavia, se vuoi, puoi restituire i valori.

InvokeMenuItem

Fornisci quindi un metodo InvokeMenuItem() che mappa le posizioni intere nell'array di elementi restituiti da GetMenuItems() alle azioni. Per l'array di elementi nell'esempio 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'enum BuiltinAction per indicare al sistema di eseguire quell'azione (o il membro NO_ACTION se non vuoi che il sistema faccia nulla). Qui puoi fornire funzionalità di recupero aggiuntive rispetto a quelle presenti nel sistema: aggiungi un elemento al 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 vari luoghi. Per maggiori dettagli, consulta la sezione Sideload.
  • WIPE_CACHE. Formatta solo la partizione della cache. Non è richiesta alcuna conferma in quanto si tratta di un'azione relativamente innocua.
  • WIPE_DATA. Riformatta le partizioni userdata e 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 recupero 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 utente e le partizioni della cache vengano cancellati. Se il dispositivo memorizza i dati utente in un luogo diverso da queste due partizioni, devi cancellarli qui. Devi restituire 0 per indicare il successo e un altro valore per l'errore, anche se al momento il valore restituito viene ignorato. Le partizioni dei dati utente e della cache vengono cancellate indipendentemente dal fatto che venga restituito un valore di successo o di errore.

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

Marca del dispositivo

Infine, includi del 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, compilalo e collegalo al ripristino sul dispositivo. In Android.mk, crea una libreria statica contenente 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 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 aggiornamento normale, lo smartphone si avvia in modalità di ripristino, completa la barra di avanzamento dell'installazione e si riavvia nel nuovo sistema senza input da parte dell'utente. In caso di un problema di aggiornamento del sistema, l'unica azione che l'utente può intraprendere è chiamare l'assistenza clienti.

Un'interfaccia solo di immagini elimina la necessità di localizzazione. Tuttavia, a partire da Android 5.0, l'aggiornamento può mostrare una stringa di testo (ad es. "Installazione aggiornamento di sistema…") insieme all'immagine. Per maggiori dettagli, vedi Testo di recupero localizzato.

Android 5.0 e versioni successive

L'interfaccia utente di Android 5.0 e versioni successive utilizza due immagini principali: l'immagine di errore e l'animazione di 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 un'unica immagine PNG con diversi frame dell'animazione interlacciati per riga (per questo motivo la Figura 2 appare schiacciata). Ad esempio, per un'animazione di sette fotogrammi 200 x 200, crea una singola immagine 200 x 1400 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 e così via. L'immagine combinata include un frammento 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 utilizzata dal 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'interfaccia utente del recupero di Android 4.x e versioni precedenti utilizza l'immagine di errore (mostrata sopra) e l'animazione di installazione, oltre a diverse immagini in overlay:

immagine mostrata durante l&#39;installazione OTA

Figura 3. icon_installing.png

immagine mostrata come primo overlay

Figura 4. icon-installing_overlay01.png

immagine mostrata come settimo overlay

Figura 5. icon_installing_overlay07.png

Durante l'installazione, la visualizzazione sullo schermo viene creata disegnando l'immagine icon_installing.png, quindi uno dei frame di overlay sopra di essa con l'offset corretto. Qui, una casella rossa è sovrapposta per evidenziare la posizione dell'overlay sull'immagine di base:

immagine composita del primo overlay di install plus

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

immagine composita dell&#39;installazione più il 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 in overlay successiva sopra quella già presente; l'immagine di base non viene ridisegnata.

Il numero di fotogrammi dell'animazione, la velocità desiderata e gli offset x e y dell'overlay rispetto alla base sono impostati dalle variabili membro della classe ScreenRecoveryUI. Quando utilizzi immagini personalizzate anziché quelle predefinite, sostituisci il metodo Init() nella sottoclasse per modificare questi valori per le immagini personalizzate (per maggiori dettagli, consulta ScreenRecoveryUI). Lo script bootable/recovery/make-overlay.py può aiutarti a convertire un insieme di frame di immagini nel formato "immagine di base + immagini in 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 solo 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 recupero localizzato

Android 5.x mostra una stringa di testo (ad es. "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 di riga di comando al 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 di stringhe di testo di recupero:

immagine del testo di recupero

Figura 8. Testo localizzato per i messaggi di risposta

Il messaggio di recupero può mostrare i seguenti messaggi:

  • Installazione dell'aggiornamento di sistema in corso…
  • Errore!
  • Reset in corso… (durante l'eliminazione dei dati/il ripristino dei dati di fabbrica)
  • Nessun comando (quando un utente avvia il 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 avvia manualmente il ripristino, le impostazioni internazionali potrebbero non essere disponibili e non viene visualizzato alcun testo. Non rendere i messaggi fondamentali per il processo 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 principale (o l'animazione). La barra di avanzamento viene 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 riempita 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 riportate sopra, viene visualizzato:

barra di avanzamento all&#39;1%

Figura 11. Barra di avanzamento al 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 il 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 quella directory, il sistema di compilazione lo utilizza in preferenza all'immagine predefinita corrispondente. Sono supportati solo i file PNG in formato RGB o RGBA con una profondità di colore di 8 bit.

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

Dispositivi senza schermo

Non tutti i dispositivi Android sono dotati di schermo. Se il tuo dispositivo è un'appliance senza interfaccia o ha un'interfaccia solo audio, potrebbe essere necessario eseguire una personalizzazione più completa dell'interfaccia utente di ripristino. Invece di creare una sottoclasse di ScreenRecoveryUI, crea una sottoclasse direttamente della classe principale RecoveryUI.

RecoveryUI dispone di metodi per gestire operazioni dell'interfaccia utente di livello inferiore, come "attiva/disattiva il display", "aggiorna la barra di avanzamento", "mostra il menu", "modifica la selezione del menu" e così via. Puoi sostituire queste operazioni per fornire un'interfaccia appropriata per il tuo dispositivo. Forse il tuo dispositivo ha LED su cui puoi usare diversi colori o pattern di lampeggio per indicare lo stato oppure puoi riprodurre audio. Forse non vuoi supportare un menu o la modalità di "visualizzazione di testo "; puoi impedire l'accesso con le implementazioni di CheckKey() e HandleMenuKey() che non attivano mai la visualizzazione o selezionano un elemento del menu. In questo caso, molti dei metodi RecoveryUI che devi fornire possono essere semplicemente stub vuoti.

Consulta bootable/recovery/ui.h per la dichiarazione di RecoveryUI per sapere quali metodi devi supportare. RecoveryUI è astratto: alcuni metodi sono puramente virtuali e devono essere forniti dalle classi di sottoclasse, ma contiene il codice per l'elaborazione degli input principali. Puoi anche sostituire questo valore se il tuo dispositivo non ha chiavi o se vuoi elaborarle in modo diverso.

Google Updater

Puoi utilizzare codice specifico del dispositivo per l'installazione del pacchetto di aggiornamento fornendo le tue funzioni di estensione che possono essere chiamate dallo script di aggiornamento. Ecco un esempio di funzione 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 nuovo Value* 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);
    }

Gli argomenti non sono stati valutati al momento della chiamata della funzione: la logica della funzione determina quali vengono valutati e quante volte. Di conseguenza, puoi utilizzare le funzioni di estensione per implementare le tue strutture di controllo. Call Evaluate() per valutare un argomento Expr* , restituendo un Value*. Se Evaluate() restituisce NULL, devi liberare le risorse che stai utilizzando e restituire immediatamente NULL (in questo modo gli aborti vengono propagati fino allo stack edify). In caso contrario, acquisisci la proprietà del valore restituito e sei responsabile di chiamare eventualmente FreeValue().

Supponiamo che la funzione abbia bisogno di due argomenti: una chiave con valore stringa e un 'immagine con valore blob. Potresti leggere gli argomenti come segue:

   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 della presenza di valori NULL e la liberazione degli argomenti valutati in precedenza può diventare noioso per più argomenti. La funzione ReadValueArgs() può semplificare questa operazione. Invece del codice riportato sopra, avresti potuto scrivere:

   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ù pratico farlo con un'istruzione if a costo di produrre un messaggio di errore leggermente 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 utilità 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 verrà passata al chiamante. L'autore della chiamata acquisisce la proprietà di tutti i dati a cui rimanda questo Value*, in particolare il membro dati.

In questo caso, devi restituire un valore true o false per indicare il successo. Ricorda la convention per 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 gli 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 utilità StringValue() inserisce una stringa in un nuovo oggetto Value. Da utilizzare per scrivere il codice riportato sopra 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 alle funzioni specifiche del dispositivo il nome device.whatever per evitare conflitti con le funzioni incorporate aggiunte in futuro.

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

Ora puoi configurare il file make per creare una libreria statica con il tuo codice. Si tratta dello stesso file make utilizzato per personalizzare l'interfaccia utente di ripristino nella sezione precedente. Sul tuo dispositivo potrebbero essere definite entrambe le librerie statiche.

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 in essa contenuta.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Infine, configura la compilazione del recupero per importare la tua raccolta. Aggiungi la tua raccolta a TARGET_RECOVERY_UPDATER_LIBS (che può contenere più librerie, tutte registrate). Se il 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, dovresti includere 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 dispositivo Tardis, lo script di aggiornamento potrebbe contenere: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Viene utilizzata la versione con un solo argomento della funzione package_extract_file() integrata, 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

Il componente finale è far sì che gli strumenti di generazione del pacchetto OTA vengano a conoscenza dei tuoi dati specifici del dispositivo ed emettano script di aggiornamento che includono chiamate alle tue funzioni di estensione.

Innanzitutto, devi comunicare al sistema di compilazione un blob di dati specifico per il dispositivo. Supponendo che il file di dati sia 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, devi aggiungere il file tardis.dat solo durante la compilazione 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 file sono chiamati file radio per motivi storici e potrebbero non avere nulla a che fare con la radio del dispositivo (se presente). Si tratta semplicemente di blob di dati opachi che il sistema di compilazione copia nel file ZIP di destinazione utilizzato dagli strumenti di generazione OTA. Quando esegui una compilazione, 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 di cui hai bisogno).

FullOTA_Assertions()
Viene chiamato all'inizio della generazione di un'OTA completa. Questo è un buon punto per emettere asserzioni sullo stato corrente del dispositivo. Non emettere comandi dello script che apportano modifiche al dispositivo.
FullOTA_InstallBegin()
Viene chiamato dopo che tutte le verifiche relative allo 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 altro elemento sul dispositivo venga modificato.
FullOTA_InstallEnd()
Viene 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 viene chiamato durante la generazione di un pacchetto di aggiornamenti incrementali.
IncrementalOTA_VerifyBegin()
Viene chiamato dopo che tutte le verifiche relative allo 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 altro elemento sul dispositivo sia stato modificato.
IncrementalOTA_VerifyEnd()
Viene chiamato al termine della fase di verifica, quando lo script ha terminato di confermare che i file che sta per modificare hanno i contenuti iniziali previsti. A questo punto non è stato modificato nulla sul dispositivo. Puoi anche emettere codice per verifiche aggiuntive specifiche per il dispositivo.
IncrementalOTA_InstallBegin()
Viene chiamato dopo che è stato verificato che i file da correggere hanno lo stato prima previsto, ma prima che vengano apportate modifiche. Puoi emettere comandi per aggiornamenti specifici del dispositivo che devono essere eseguiti prima che qualsiasi altro elemento del dispositivo venga modificato.
IncrementalOTA_InstallEnd()
Analogamente alla controparte del pacchetto OTA completo, viene chiamato alla fine 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 gli aggiornamenti specifici del dispositivo.

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

Passare funzioni agli oggetti info

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

  • info.input_zip. (Solo OTA complete) L'oggetto zipfile.ZipFile per i file di destinazione .zip di input.
  • info.source_zip. (Solo OTA incrementali) L'oggetto zipfile.ZipFile per i file di destinazione .zip di origine (la build già sul dispositivo al momento dell'installazione del pacchetto incrementale).
  • info.target_zip. (Solo OTA incrementali) L'oggetto zipfile.ZipFile per il file .zip target-files di destinazione (la build che il pacchetto incrementale inserisce 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 dello script a cui puoi aggiungere comandi. Chiama info.script.AppendExtra(script_text) per visualizzare il testo nello script. Assicurati che il testo di output termini con un punto e virgola in modo che non entri in conflitto con i comandi emessi successivamente.

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

Specifica 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, per impostazione predefinita viene utilizzata la directory $(TARGET_DEVICE_DIR)/../common (device/yoyodyne/common in questo esempio). È preferibile definire esplicitamente la posizione dello script releasetools.py. Durante la compilazione 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 .zip dei file di destinazione, se presente, è preferito a quello della struttura di origine 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 alle estensioni di releasetools e applicarle ai vecchi file di destinazione.

Ora, quando esegui ota_from_target_files, viene rilevato automaticamente il modulo specifico per il dispositivo dal file .zip target_files e viene utilizzato per 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 per il 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 recupero ha un meccanismo di sideload per installare manualmente un pacchetto di aggiornamento senza scaricarlo over-the-air dal sistema principale. Il sideload è utile per eseguire il debug o apportare modifiche su dispositivi su cui non è possibile avviare il sistema principale.

In passato, il sideload veniva eseguito caricando i pacchetti dalla scheda SD del dispositivo. Nel caso di un dispositivo che non si avvia, il pacchetto può essere caricato sulla scheda SD utilizzando un altro computer, dopodiché la scheda SD viene inserita nel dispositivo. Per supportare i dispositivi Android senza archiviazione esterna rimovibile, il recupero supporta due meccanismi aggiuntivi per il sideload: carregare i pacchetti dalla partizione della cache e caricarli tramite USB utilizzando adb.

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

  • APPLY_EXT. Esegui il sideload di un pacchetto di aggiornamento dall'unità di archiviazione esterna (directory /sdcard). Il file recovery.fstab deve definire il punto di montaggio /sdcard . Questa opzione non è utilizzabile sui dispositivi che simulano una scheda SD con un link simbolico a /data (o un meccanismo simile). In genere, /data 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 (sempre disponibile per il recupero). Dal sistema normale, /cache è scrivibile solo da utenti con privilegi, e 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 viene invocato questo meccanismo, il ripristino avvia la propria mini versione del demone adbd per consentire ad adb su un computer host connesso di comunicare con esso. Questa versione mini 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 nello spazio di archiviazione locale.

Alcune avvertenze:

  • È supportato solo il trasporto USB.
  • Se il recupero esegue normalmente adbd (di solito è vero per le build userdebug ed eng), verrà chiuso mentre il dispositivo è in modalità sideload adb e verrà riavviato al termine della ricezione di un pacchetto da parte di adb sideload. In modalità sideload adb, nessun comando adb diverso da sideload funziona ( logcat, reboot, push, pull, shell e così via non vanno a buon fine).
  • Non puoi uscire dalla modalità sideload adb sul dispositivo. Per annullare l'operazione, puoi inviare /dev/null (o qualsiasi altro pacchetto non valido) come pacchetto, quindi 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, quindi puoi fornire una sequenza di tasti che riavvia il dispositivo e funziona in modalità sideload adb.