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(); }
Crea e collega il recupero del dispositivo
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.
![]() Figura 1. icon_error.png |
![]() 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:
![]() Figura 3. icon_installing.png |
![]() Figura 4. icon-installing_overlay01.png |
![]() 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:
![]() Figura 6. Installazione del frame dell'animazione 1 (icon_installing.png + icon_installing_overlay01.png) |
![]() 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:

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:

Figura 9. progress_empty.png

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:

Figura 11. Barra di avanzamento al 1%>

Figura 12. Barra di avanzamento al 10%

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 metodoCheckKey()
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.