O sistema de recuperação inclui vários hooks para inserir código específico do dispositivo para que as atualizações OTA também possam atualizar partes do dispositivo que não sejam o sistema Android (por exemplo, a banda base ou o processador de rádio).
As seções e exemplos a seguir personalizam o dispositivo tardis produzido pelo fornecedor yoyodyne.
Mapa de partição
Desde o Android 2.3, a plataforma oferece suporte a dispositivos de memória flash eMMC e ao sistema de arquivos ext4 que é executado nesses dispositivos. Ele também oferece suporte a dispositivos flash MTD (Memory Technology Device) e ao sistema de arquivos yaffs2 de versões mais antigas.
O arquivo de mapa de partição é especificado por TARGET_RECOVERY_FSTAB e usado pelo binário de recuperação e pelas ferramentas de criação de pacotes. Você pode especificar o nome do arquivo de mapa em TARGET_RECOVERY_FSTAB em BoardConfig.mk.
Um exemplo de arquivo de mapa de partição pode ser assim:
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
Com exceção de /sdcard
, que é opcional, todos os pontos de montagem neste exemplo precisam ser definidos (os dispositivos também podem adicionar partições extras). Há cinco tipos de sistemas de arquivos compatíveis:
- yaffs2
-
Um sistema de arquivos yaffs2 em cima de um dispositivo flash MTD. "device" precisa ser o nome da partição MTD
e aparecer em
/proc/mtd
. - mtd
-
Uma partição MTD bruta, usada para partições inicializáveis, como inicialização e recuperação. O MTD não é montado, mas o ponto de montagem é usado como uma chave para localizar a partição. "device"
precisa ser o nome da partição MTD em
/proc/mtd
. - ext4
- Um sistema de arquivos ext4 em cima de um dispositivo flash eMMc. "device" precisa ser o caminho do dispositivo de bloco.
- emmc
- Um dispositivo de bloco eMMc bruto usado para partições inicializáveis, como inicialização e recuperação. Assim como o tipo mtd, o eMMC nunca é montado, mas a string do ponto de montagem é usada para localizar o dispositivo na tabela.
- vfat
-
Um sistema de arquivos FAT em cima de um dispositivo de bloco, geralmente para armazenamento externo, como um cartão SD. O
dispositivo é o dispositivo de bloco. O dispositivo2 é um segundo dispositivo de bloco que o sistema tenta montar se
a montagem do dispositivo principal falhar (para compatibilidade com cartões SD que podem ou não ser
formatados com uma tabela de partição).
Todas as partições precisam ser montadas no diretório raiz. Ou seja, o valor do ponto de montagem precisa começar com uma barra e não ter outras barras. Essa restrição se aplica apenas à montagem de sistemas de arquivos na recuperação. O sistema principal pode montá-los em qualquer lugar. Os diretórios
/boot
,/recovery
e/misc
precisam ser tipos brutos (mtd ou emmc), enquanto os diretórios/system
,/data
,/cache
e/sdcard
(se disponíveis) precisam ser tipos de sistema de arquivos (yaffs2, ext4 ou vfat).
A partir do Android 3.0, o arquivo recovery.fstab ganha um campo opcional adicional, options. No momento, a única opção definida é length , que permite especificar explicitamente o comprimento da partição. Esse comprimento é usado ao reformatar a partição (por exemplo, para a partição userdata durante uma operação de limpeza de dados/restauração de fábrica ou para a partição do sistema durante a instalação de um pacote OTA completo). Se o valor do comprimento for negativo, o tamanho a ser formatado será obtido adicionando o valor do comprimento ao tamanho real da partição. Por exemplo, definir "length=-16384" significa que os últimos 16k dessa partição não serão substituídos quando ela for reformatada. Isso oferece suporte a recursos como criptografia da partição userdata (em que os metadados de criptografia são armazenados no final da partição que não deve ser substituída).
Observação:os campos device2 e options são opcionais, o que cria ambiguidade na análise. Se a entrada no quarto campo da linha começar com um caractere "/", ela será considerada uma entrada device2. Se não começar com esse caractere, ela será considerada um campo options.
Animação de inicialização
Os fabricantes de dispositivos podem personalizar a animação mostrada quando um dispositivo Android está sendo inicializado. Para fazer isso, crie um arquivo .zip organizado e localizado de acordo com as especificações em formato bootanimation.
Para dispositivos Android Things, faça upload do arquivo ZIP no console do Android Things para incluir as imagens no produto selecionado.
Observação:essas imagens precisam obedecer às diretrizes da marca Android. Para conferir diretrizes de marca, consulte a seção sobre o Android no Partner Marketing Hub.
Interface de recuperação
Para oferecer suporte a dispositivos com diferentes hardwares disponíveis (botões físicos, LEDs, telas etc.), É possível personalizar a interface de recuperação para mostrar o status e acessar os recursos ocultos operados manualmente de cada dispositivo.
Seu objetivo é criar uma pequena biblioteca estática com alguns objetos C++ para fornecer a
funcionalidade específica do dispositivo. O arquivo
bootable/recovery/default_device.cpp
é usado por padrão e é um bom
ponto de partida para copiar ao gravar uma versão desse arquivo para seu dispositivo.
Observação:talvez apareça uma mensagem dizendo Nenhum comando aqui. Para alternar o texto, mantenha pressionado o botão liga/desliga e toque no botão de aumentar o volume. Se o dispositivo não tiver os dois botões, pressione e mantenha pressionado qualquer botão para alternar o texto.
device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h> #include "common.h" #include "device.h" #include "screen_ui.h"
Funções de cabeçalho e item
A classe "Device" exige funções para retornar cabeçalhos e itens que aparecem no menu de recuperação oculto. Os cabeçalhos descrevem como operar o menu (ou seja, controles para mudar/selecionar o item destacado).
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 };
Observação:as linhas longas são truncadas (não quebradas), então considere a largura da tela do dispositivo.
Personalizar CheckKey
Em seguida, defina a implementação da RecoveryUI do dispositivo. Este exemplo pressupõe que o dispositivo
tardis tem uma tela. Assim, é possível herdar da
implementação ScreenRecoveryUI integrada. Consulte as instruções para
dispositivos sem tela. A única função para
personalizar da ScreenRecoveryUI é CheckKey()
, que faz o processamento
assíncrono inicial de chaves:
class TardisUI : public ScreenRecoveryUI { public: virtual KeyAction CheckKey(int key) { if (key == KEY_HOME) { return TOGGLE; } return ENQUEUE; } };
Constantes KEY
As constantes KEY_* são definidas em linux/input.h
. CheckKey()
é
chamado não importa o que esteja acontecendo no restante da recuperação: quando o menu é desativado, quando
ele está ativado, durante a instalação do pacote, durante a exclusão permanente de dados do usuário etc. Ele pode retornar uma de quatro
constantes:
- ATIVAR/DESATIVAR. Ativar ou desativar a exibição do menu e/ou do registro de texto
- REINICIAR. Reiniciar o dispositivo imediatamente
- IGNORAR. Ignorar este pressionamento de tecla
- ENQUEUE. Enfileira esse pressionamento de tecla para ser consumido de forma síncrona (ou seja, pelo sistema de menu de recuperação se a tela estiver ativada).
CheckKey()
é chamado sempre que um evento de tecla pressionada é seguido por um evento de tecla liberada para
a mesma tecla. A sequência de eventos A-down B-down B-up A-up resulta apenas na chamada de
CheckKey(B)
. CheckKey()
pode chamar
IsKeyPressed()
para descobrir se outras teclas estão pressionadas. Na sequência de eventos de tecla acima, se CheckKey(B)
chamasse IsKeyPressed(A)
, ele retornaria "true".
O CheckKey()
pode manter o estado na classe dele, o que é útil para detectar
sequências de chaves. Este exemplo mostra uma configuração um pouco mais complexa: a tela é ativada/desativada ao
manter pressionado o botão liga/desliga e pressionar o botão de aumentar volume. O dispositivo pode ser reinicializado imediatamente ao
pressionar o botão liga/desliga cinco vezes seguidas (sem outras teclas intermediárias):
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
Ao usar suas próprias imagens (ícone de erro, animação de instalação, barras de progresso) com
ScreenRecoveryUI, é possível definir a variável animation_fps
para controlar a velocidade em
quadros por segundo (QPS) das animações.
Observação:o script interlace-frames.py
atual permite armazenar as informações animation_fps
na própria imagem. Em versões anteriores do
Android, era necessário definir animation_fps
por conta própria.
Para definir a variável animation_fps
, substitua a função ScreenRecoveryUI::Init()
na subclasse. Defina o valor e chame a
função parent Init()
para concluir a inicialização. O valor padrão (20 FPS) corresponde às imagens de recuperação padrão. Ao usar essas imagens, não é necessário fornecer uma função Init()
. Para mais detalhes sobre imagens, consulte
Imagens da interface de recuperação.
Classe do dispositivo
Depois de ter uma implementação do RecoveryUI, defina a classe do dispositivo (subclasse da
classe Device integrada). Ela precisa criar uma única instância da classe de UI e retornar essa
da função GetUI()
:
class TardisDevice : public Device { private: TardisUI* ui; public: TardisDevice() : ui(new TardisUI) { } RecoveryUI* GetUI() { return ui; }
StartRecovery
O método StartRecovery()
é chamado no início da recuperação, depois que a interface foi
inicializada e os argumentos foram analisados, mas antes de qualquer ação ser
realizada. A implementação padrão não faz nada. Portanto, não é necessário fornecer isso na sua
subclasse se você não tiver nada a fazer:
void StartRecovery() { // ... do something tardis-specific here, if needed .... }
Fornecer e gerenciar o menu de recuperação
O sistema chama dois métodos para receber a lista de linhas de cabeçalho e a lista de itens. Nesta implementação, ele retorna as matrizes estáticas definidas na parte de cima do arquivo:
const char* const* GetMenuHeaders() { return HEADERS; } const char* const* GetMenuItems() { return ITEMS; }
HandleMenuKey
Em seguida, forneça uma função HandleMenuKey()
, que recebe uma tecla pressionada e a visibilidade atual do menu e decide qual ação realizar:
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; }
O método usa um código de tecla (que foi processado e enfileirado anteriormente pelo
método CheckKey()
do objeto da interface) e o estado atual da visibilidade
do menu/registro de texto. O valor de retorno é um número inteiro. Se o valor for 0 ou maior, ele será considerado a
posição de um item de menu, que é invocado imediatamente (consulte o
método InvokeMenuItem()
abaixo). Caso contrário, pode ser uma das seguintes constantes predefinidas:
- kHighlightUp. Mover o destaque do menu para o item anterior
- kHighlightDown. Mover o destaque do menu para o próximo item
- kInvokeItem. Invocar o item destacado
- kNoAction. Não fazer nada com essa tecla
Como implícito pelo argumento visível, HandleMenuKey()
é chamado mesmo que o menu não esteja visível. Ao contrário de CheckKey()
, ele não é chamado enquanto a recuperação está fazendo
algo como limpar dados ou instalar um pacote. Ele é chamado apenas quando a recuperação está inativa
e aguardando entrada.
Mecanismos de trackball
Se o dispositivo tiver um mecanismo de entrada semelhante a uma trackball (gera eventos de entrada com tipo EV_REL
e código REL_Y), a recuperação sintetiza pressionamentos de tecla KEY_UP e KEY_DOWN sempre que o
dispositivo de entrada semelhante a uma trackball informa movimento no eixo Y. Basta mapear os eventos KEY_UP e KEY_DOWN para ações do menu. Esse mapeamento não acontece para
CheckKey()
. Portanto, não é possível usar movimentos da trackball como gatilhos para reiniciar ou
ativar/desativar a tela.
Teclas modificadoras
Para verificar se as teclas estão pressionadas como modificadores, chame o método IsKeyPressed()
do seu próprio objeto de interface. Por exemplo, em alguns dispositivos, pressionar Alt-W na recuperação iniciava uma
limpeza de dados, mesmo que o menu não estivesse visível. Você pode implementar assim:
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 } ... }
Observação:se visible for falso, não faz sentido retornar os valores especiais que manipulam o menu (mover destaque, invocar item destacado), já que o usuário não pode ver o destaque. No entanto, é possível retornar os valores, se quiser.
InvokeMenuItem
Em seguida, forneça um método InvokeMenuItem()
que mapeie posições inteiras na matriz
de itens retornados por GetMenuItems()
para ações. Para a matriz de itens no exemplo
tardis, use:
BuiltinAction InvokeMenuItem(int menu_position) { switch (menu_position) { case 0: return REBOOT; case 1: return APPLY_ADB_SIDELOAD; case 2: return WIPE_DATA; case 3: return WIPE_CACHE; default: return NO_ACTION; } }
Esse método pode retornar qualquer membro da enumeração BuiltinAction para informar ao sistema que ele precisa realizar essa ação (ou o membro NO_ACTION se você não quiser que o sistema faça nada). É aqui que você fornece funcionalidade de recuperação adicional além do que está no sistema: adicione um item para isso no menu, execute-o aqui quando esse item de menu for invocado e retorne NO_ACTION para que o sistema não faça mais nada.
BuiltinAction contém os seguintes valores:
- NO_ACTION. Não fazer nada.
- REINICIAR. Saia da recuperação e reinicie o dispositivo normalmente.
- APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Instale um pacote de atualização de vários lugares. Para mais detalhes, consulte Sideloading.
- WIPE_CACHE. Reformatar apenas a partição de cache. Nenhuma confirmação é necessária, já que isso é relativamente inofensivo.
- WIPE_DATA. Reformatar as partições userdata e cache, também conhecida como redefinição de dados de fábrica. O usuário precisa confirmar essa ação antes de continuar.
O último método, WipeData()
, é opcional e é chamado sempre que uma operação de limpeza de dados
é iniciada (pela recuperação no menu ou quando o usuário escolhe fazer uma
redefinição de dados de fábrica no sistema principal). Esse método é chamado antes que os dados do usuário e as partições de cache
sejam apagados. Se o dispositivo armazenar dados do usuário em qualquer outro lugar que não seja essas duas partições, apague-os aqui. Retorne 0 para indicar sucesso e outro valor para falha, embora atualmente o valor de retorno seja ignorado. Os dados do usuário e as partições de cache são apagados, seja qual for o resultado.
int WipeData() { // ... do something tardis-specific here, if needed .... return 0; }
Criar dispositivo
Por fim, inclua um modelo no final do arquivo recovery_ui.cpp para a
função make_device()
que cria e retorna uma instância da sua classe Device:
class TardisDevice : public Device { // ... all the above methods ... }; Device* make_device() { return new TardisDevice(); }
Criar e vincular à recuperação do dispositivo
Depois de concluir o arquivo recovery_ui.cpp, crie e vincule-o à recuperação no seu dispositivo. Em Android.mk, crie uma biblioteca estática que contenha apenas este arquivo 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)
Em seguida, na configuração da placa para esse dispositivo, especifique sua biblioteca estática como o valor de TARGET_RECOVERY_UI_LIB.
device/yoyodyne/tardis/BoardConfig.mk [...] # device-specific extensions to the recovery UI TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis
Imagens da interface de recuperação
A interface do usuário de recuperação consiste em imagens. O ideal é que os usuários nunca interajam com a interface: durante uma atualização normal, o smartphone inicializa a recuperação, preenche a barra de progresso da instalação e inicializa novamente o novo sistema sem entrada do usuário. Em caso de um problema de atualização do sistema, a única ação que o usuário pode realizar é entrar em contato com o atendimento ao cliente.
Uma interface somente de imagem elimina a necessidade de localização. No entanto, a partir do Android 5.0, a atualização pode mostrar uma string de texto (por exemplo, "Instalando atualização do sistema...") junto com a imagem. Para mais detalhes, consulte Texto de recuperação localizado.
Android 5.0 e mais recente
A interface de recuperação do Android 5.0 e versões mais recentes usa duas imagens principais: a imagem de erro e a animação de instalação.
![]() Figura 1. icon_error.png |
![]() Figura 2. icon_installing.png |
A animação de instalação é representada como uma única imagem PNG com diferentes frames da animação entrelaçados por linha. Por isso, a Figura 2 parece achatada. Por exemplo, para uma animação de sete frames de 200 x 200, crie uma única imagem de 200 x 1.400 em que o primeiro frame seja as linhas 0, 7, 14, 21 etc., o segundo frame seja as linhas 1, 8, 15, 22 etc. A imagem combinada inclui um bloco de texto que indica o número de frames da animação e o número de frames por segundo (FPS). A ferramenta bootable/recovery/interlace-frames.py
usa um conjunto de frames de entrada
e os combina na imagem composta necessária usada pela recuperação.
As imagens padrão estão disponíveis em diferentes densidades e estão localizadas em
bootable/recovery/res-$DENSITY/images
(por exemplo,
bootable/recovery/res-hdpi/images
). Para usar uma imagem estática durante a instalação,
basta fornecer a imagem icon_installing.png e definir o número de frames na
animação como 0. O ícone de erro não é animado, ele é sempre uma imagem estática.
Android 4.x e versões anteriores
A interface de recuperação do Android 4.x e versões anteriores usa a imagem de erro (mostrada acima) e a animação de instalação, além de várias imagens de sobreposição:
![]() Figura 3. icon_installing.png |
![]() Figura 4.icon-installing_overlay01.png |
![]() Ícone Figura 5 icon_installing_overlay07.png |
Durante a instalação, a tela é construída desenhando a imagem icon_installing.png e um dos frames de sobreposição em cima dela no deslocamento adequado. Aqui, uma caixa vermelha é sobreposta para destacar onde a sobreposição é colocada em cima da imagem de base:
![]() Figura 6. Instalando o frame 1 da animação (icon_installing.png + icon_installing_overlay01.png) |
![]() Figura 7. Instalando o frame de animação 7 (icon_installing.png + icon_installing_overlay07.png) |
Os frames subsequentes são mostrados desenhando apenas a próxima imagem de sobreposição sobre o que já está lá. A imagem de base não é redesenhada.
O número de frames na animação, a velocidade desejada e os deslocamentos x e y da sobreposição em relação à base são definidos por variáveis de membro da classe ScreenRecoveryUI. Ao usar imagens personalizadas em vez das padrão, substitua o método Init()
na subclasse para mudar esses valores nas imagens personalizadas. Para mais detalhes, consulte ScreenRecoveryUI. O script
bootable/recovery/make-overlay.py
pode ajudar a converter um conjunto de frames de imagem
no formato "imagem de base + imagens de sobreposição" necessário para a recuperação, incluindo o cálculo dos
deslocamentos necessários.
As imagens padrão estão localizadas em bootable/recovery/res/images
. Para usar uma imagem estática
durante a instalação, basta fornecer a imagem icon_installing.png e definir o número de
frames na animação como 0. O ícone de erro não é animado, ele é sempre uma imagem estática.
Texto de recuperação localizado
O Android 5.x mostra uma string de texto (por exemplo, Instalando atualização do sistema...") junto com a imagem. Quando o sistema principal é inicializado na recuperação, ele transmite a localidade atual do usuário como uma opção de linha de comando para a recuperação. Para cada mensagem a ser exibida, a recuperação inclui uma segunda imagem composta com strings de texto pré-renderizadas para essa mensagem em cada localidade.
Exemplo de imagem de strings de texto de recuperação:

Figura 8. Texto localizado para mensagens de recuperação
O texto de recuperação pode mostrar as seguintes mensagens:
- Instalando atualização do sistema…
- Erro!
- Apagando… (ao limpar os dados/fazer uma redefinição de fábrica)
- Nenhum comando (quando um usuário inicializa a recuperação manualmente)
O app Android em bootable/recovery/tools/recovery_l10n/
renderiza localizações
de uma mensagem e cria a imagem composta. Para detalhes sobre como usar esse app, consulte os comentários em bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
.
Quando um usuário inicializa a recuperação manualmente, a localidade pode não estar disponível e nenhum texto é mostrado. Não torne as mensagens de texto essenciais para o processo de recuperação.
Observação:a interface oculta que mostra mensagens de registro e permite que o usuário selecione ações no menu está disponível apenas em inglês.
Barras de progresso
As barras de progresso podem aparecer abaixo da imagem (ou animação) principal. A barra de progresso é feita combinando duas imagens de entrada, que precisam ter o mesmo tamanho:

Figura 9.progress_empty.png

Figura 10.progress_fill.png
A extremidade esquerda da imagem de preenchimento é mostrada ao lado da extremidade direita da imagem de vazio para criar a barra de progresso. A posição do limite entre as duas imagens muda para indicar o progresso. Por exemplo, com os pares de imagens de entrada acima, mostre:

Figura 11. Barra de progresso em 1%>

Figura 12. Barra de progresso em 10%

Figura 13. Barra de progresso em 50%
Você pode fornecer versões específicas do dispositivo dessas imagens colocando-as em (neste exemplo) device/yoyodyne/tardis/recovery/res/images
. Os nomes dos arquivos precisam corresponder aos listados acima. Quando um arquivo é encontrado nesse diretório, o
sistema de build o usa em vez da imagem padrão correspondente. Somente PNGs nos formatos RGB ou RGBA com profundidade de cor de 8 bits são aceitos.
Observação:no Android 5.x, se a localidade for conhecida pela recuperação e for um idioma da direita para a esquerda (RTL), como árabe, hebraico etc., a barra de progresso será preenchida da direita para a esquerda.
Dispositivos sem tela
Nem todos os dispositivos Android têm telas. Se o dispositivo for um eletrodoméstico sem tela ou tiver uma interface somente de áudio, talvez seja necessário personalizar mais a interface de recuperação. Em vez de criar uma subclasse de ScreenRecoveryUI, crie uma subclasse da classe mãe RecoveryUI diretamente.
A RecoveryUI tem métodos para lidar com operações de interface de nível inferior, como "alternar a tela",
"atualizar a barra de progresso", "mostrar o menu", "mudar a seleção do menu" etc. Você pode substituir
essas opções para fornecer uma interface adequada ao seu dispositivo. Talvez seu dispositivo tenha LEDs em que
você pode usar cores ou padrões de piscagem diferentes para indicar o estado, ou talvez seja possível tocar
áudio. Talvez você não queira oferecer suporte a um menu ou ao modo de "exibição de texto". É possível
impedir o acesso a eles com implementações CheckKey()
e
HandleMenuKey()
que nunca ativam a exibição nem selecionam um item de
menu. Nesse caso, muitos dos métodos RecoveryUI que você precisa fornecer podem ser apenas stubs vazios.)
Consulte bootable/recovery/ui.h
para a declaração de RecoveryUI e saiba quais métodos
você precisa oferecer suporte. A RecoveryUI é abstrata. Alguns métodos são virtuais puros e precisam ser fornecidos por
subclasses, mas ela contém o código para processar entradas importantes. Você também pode substituir isso se o dispositivo não tiver chaves ou se quiser processá-las de maneira diferente.
Updater
Você pode usar código específico do dispositivo na instalação do pacote de atualização fornecendo suas próprias funções de extensão que podem ser chamadas no script do atualizador. Confira um exemplo de função para o dispositivo tardis:
device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h> #include <string.h> #include "edify/expr.h"
Todas as funções de extensão têm a mesma assinatura. Os argumentos são o nome pelo qual a função foi chamada, um cookie State*
, o número de argumentos recebidos e uma matriz de ponteiros Expr*
que representam os argumentos. O valor de retorno é um
Value*
recém-alocado.
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); }
Seus argumentos não são avaliados quando a função é chamada. A lógica da função determina quais deles são avaliados e quantas vezes. Assim, é possível usar funções de extensão para implementar suas próprias estruturas de controle. Call Evaluate()
para avaliar
um argumento Expr*
, retornando um Value*
. Se Evaluate()
retornar NULL, libere todos os recursos que você está mantendo e retorne NULL imediatamente (isso
propaga as interrupções na pilha do edify). Caso contrário, você assume a propriedade do valor retornado e é responsável por chamar FreeValue()
nele.
Suponha que a função precise de dois argumentos: uma chave com valor de string e uma imagem com valor de blob. Você pode ler argumentos assim:
Value* key = EvaluateValue(state, argv[0]); if (key == NULL) { return NULL; } if (key->type != VAL_STRING) { ErrorAbort(state, "first arg to %s() must be string", name); FreeValue(key); return NULL; } Value* image = EvaluateValue(state, argv[1]); if (image == NULL) { FreeValue(key); // must always free Value objects return NULL; } if (image->type != VAL_BLOB) { ErrorAbort(state, "second arg to %s() must be blob", name); FreeValue(key); FreeValue(image) return NULL; }
Verificar NULL e liberar argumentos avaliados anteriormente pode ficar cansativo para vários argumentos. A função ReadValueArgs()
pode facilitar isso. Em vez do código
acima, você poderia ter escrito isto:
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; }
O ReadValueArgs()
não faz verificação de tipos, então você precisa fazer isso aqui. É mais conveniente fazer isso com uma instrução if, mesmo que isso produza uma mensagem de erro um pouco menos específica quando falha. No entanto, ReadValueArgs()
processa a avaliação de cada argumento e libera todos os argumentos avaliados anteriormente (além de definir uma mensagem de erro útil) se alguma das avaliações falhar. É possível usar uma função de conveniência ReadValueVarArgs()
para avaliar um número variável de argumentos (ela retorna uma matriz de Value*
).
Depois de avaliar os argumentos, faça o trabalho da função:
// 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 ...
O valor de retorno precisa ser um objeto Value*
. A propriedade desse objeto será transferida para
o caller. O autor da chamada assume a propriedade de todos os dados apontados por esse
Value*
, especificamente o datamember.
Nesse caso, você quer retornar um valor verdadeiro ou falso para indicar sucesso. Lembre-se da convenção de que a string vazia é falsa e todas as outras strings são verdadeiras. Você
precisa alocar um objeto Value com uma cópia alocada da string constante para retornar, já que o
chamador vai free()
os dois. Não se esqueça de chamar FreeValue()
nos objetos que você recebeu ao avaliar seus argumentos.
FreeValue(key); FreeValue(image); Value* result = malloc(sizeof(Value)); result->type = VAL_STRING; result->data = strdup(successful ? "t" : ""); result->size = strlen(result->data); return result; }
A função de conveniência StringValue()
encapsula uma string em um novo objeto Value.
Use para escrever o código acima de forma mais concisa:
FreeValue(key); FreeValue(image); return StringValue(strdup(successful ? "t" : "")); }
Para conectar funções ao interpretador do edify, forneça a função
Register_foo
, em que foo é o nome da biblioteca estática que contém
esse código. Chame RegisterFunction()
para registrar cada função de extensão. Por
convenção, nomeie funções específicas do dispositivo device.whatever
para evitar
conflitos com futuras funções integradas adicionadas.
void Register_librecovery_updater_tardis() { RegisterFunction("tardis.reprogram", ReprogramTardisFn); }
Agora é possível configurar o makefile para criar uma biblioteca estática com seu código. (Este é o mesmo makefile usado para personalizar a interface de recuperação na seção anterior. Seu dispositivo pode ter as duas bibliotecas estáticas definidas aqui.)
device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS) LOCAL_SRC_FILES := recovery_updater.c LOCAL_C_INCLUDES += bootable/recovery
O nome da biblioteca estática precisa ser igual ao nome da função
Register_libname
contida nela.
LOCAL_MODULE := librecovery_updater_tardis include $(BUILD_STATIC_LIBRARY)
Por fim, configure o build de recuperação para extrair sua biblioteca. Adicione sua biblioteca a
TARGET_RECOVERY_UPDATER_LIBS (que pode conter várias bibliotecas; todas são registradas).
Se o código depender de outras bibliotecas estáticas que não sejam extensões do Edify (por exemplo,
Se eles não tiverem uma função Register_libname
, liste-os em TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vincular ao atualizador sem chamar a função de registro (inexistente). Por exemplo, se o código específico do dispositivo quisesse usar
zlib para descompactar dados, você incluiria libz aqui.
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 +=
Os scripts de atualização no seu pacote OTA agora podem chamar sua função como qualquer outra. Para reprogramar
o dispositivo tardis, o script de atualização pode conter:
tardis.reprogram("the-key", package_extract_file("tardis-image.dat"))
. Isso usa
a versão de argumento único da função integrada package_extract_file()
,
que retorna o conteúdo de um arquivo extraído do pacote de atualização como um blob para produzir
o segundo argumento da nova função de extensão.
Geração de pacotes OTA
O último componente é fazer com que as ferramentas de geração de pacotes OTA conheçam seus dados específicos do dispositivo e emitam scripts de atualização que incluem chamadas para as funções de extensão.
Primeiro, faça com que o sistema de build conheça um blob de dados específico do dispositivo. Supondo que o arquivo de dados esteja em device/yoyodyne/tardis/tardis.dat
, declare o seguinte em AndroidBoard.mk do dispositivo:
device/yoyodyne/tardis/AndroidBoard.mk
[...] $(call add-radio-file,tardis.dat)
Você também pode colocar em um Android.mk, mas ele precisa ser protegido por uma verificação de dispositivo, já que todos os arquivos Android.mk na árvore são carregados, não importa qual dispositivo esteja sendo criado. Se a árvore incluir vários dispositivos, você só vai querer o arquivo tardis.dat adicionado ao criar o 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
Eles são chamados de arquivos de rádio por motivos históricos, mas podem não ter nada a ver com o rádio do dispositivo (se houver). São apenas blobs opacos de dados que o sistema de build copia para o arquivo .zip de destino usado pelas ferramentas de geração de OTA. Quando você faz um build, o tardis.dat é
armazenado no target-files.zip como RADIO/tardis.dat
. Você pode chamar
add-radio-file
várias vezes para adicionar quantos arquivos quiser.
Módulo Python
Para estender as ferramentas de lançamento, escreva um módulo do Python (precisa ser chamado de releasetools.py) que as ferramentas podem chamar, se estiverem presentes. Exemplo:
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"));""")
Uma função separada processa a geração de um pacote OTA incremental. Neste exemplo, suponha que você precise reprogramar o tardis somente quando o arquivo tardis.dat for alterado entre dois builds.
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"));""")
Funções do módulo
Você pode fornecer as seguintes funções no módulo (implemente apenas as que você precisa).
FullOTA_Assertions()
- Chamado perto do início da geração de uma OTA completa. Esse é um bom lugar para emitir declarações sobre o estado atual do dispositivo. Não emita comandos de script que façam mudanças no dispositivo.
FullOTA_InstallBegin()
- Chamado depois que todas as declarações sobre o estado do dispositivo foram aprovadas, mas antes de qualquer mudança ter sido feita. Você pode emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes de qualquer outra coisa no dispositivo ser alterada.
FullOTA_InstallEnd()
- Chamado no final da geração de script, depois que os comandos de script para atualizar as partições de inicialização e do sistema foram emitidos. Também é possível emitir outros comandos para atualizações específicas do dispositivo.
IncrementalOTA_Assertions()
-
Semelhante a
FullOTA_Assertions()
, mas chamado ao gerar um pacote de atualização incremental. IncrementalOTA_VerifyBegin()
- Chamado depois que todas as declarações sobre o estado do dispositivo foram aprovadas, mas antes de qualquer mudança ser feita. Você pode emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes de qualquer outra coisa no dispositivo.
IncrementalOTA_VerifyEnd()
- Chamado ao final da fase de verificação, quando o script termina de confirmar que os arquivos que ele vai tocar têm o conteúdo inicial esperado. Neste momento, nada no dispositivo foi alterado. Também é possível emitir código para outras verificações específicas do dispositivo.
IncrementalOTA_InstallBegin()
- Chamado depois que os arquivos a serem corrigidos são verificados como tendo o estado before esperado, mas antes que qualquer mudança seja feita. Você pode emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes de qualquer outra coisa ser alterada nele.
IncrementalOTA_InstallEnd()
- Semelhante ao pacote OTA completo, esse comando é chamado no final da geração do script depois que os comandos do script para atualizar as partições de inicialização e do sistema foram emitidos. Também é possível emitir outros comandos para atualizações específicas do dispositivo.
Observação:se o dispositivo perder a energia, a instalação OTA poderá ser reiniciada do início. Esteja preparado para lidar com dispositivos em que esses comandos já foram executados, total ou parcialmente.
Transmitir funções para objetos de informações
Transmita funções para um único objeto de informações que contenha vários itens úteis:
-
info.input_zip. (Somente OTAs completas) O objeto
zipfile.ZipFile
para o .zip de arquivos de destino de entrada. -
info.source_zip. (Somente OTAs incrementais) O objeto
zipfile.ZipFile
para o .zip de arquivos de destino de origem (o build já no dispositivo quando o pacote incremental está sendo instalado). -
info.target_zip. (Somente OTAs incrementais) O objeto
zipfile.ZipFile
para o .zip de arquivos de destino (o build que o pacote incremental coloca no dispositivo). -
info.output_zip. Pacote sendo criado; um objeto
zipfile.ZipFile
aberto para gravação. Use common.ZipWriteStr(info.output_zip, filename, data) para adicionar um arquivo ao pacote. -
info.script. Objeto de script ao qual você pode anexar comandos. Chame
info.script.AppendExtra(script_text)
para gerar texto no script. Verifique se o texto de saída termina com um ponto e vírgula para não entrar em conflito com comandos emitidos depois.
Para mais detalhes sobre o objeto de informações, consulte a documentação da Python Software Foundation para arquivos ZIP.
Especificar o local do módulo
Especifique o local do script releasetools.py do dispositivo no arquivo BoardConfig.mk:
device/yoyodyne/tardis/BoardConfig.mk
[...] TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis
Se TARGET_RELEASETOOLS_EXTENSIONS não estiver definido, o padrão será o diretório
$(TARGET_DEVICE_DIR)/../common
(device/yoyodyne/common
neste exemplo). É melhor definir explicitamente o local do script releasetools.py.
Ao criar o dispositivo tardis, o script releasetools.py é incluído no arquivo .zip target-files (META/releasetools.py
).
Quando você executa as ferramentas de lançamento (img_from_target_files
ou
ota_from_target_files
), o script releasetools.py no .zip de arquivos de destino, se
presente, é preferível ao do diretório de origem do Android. Também é possível especificar explicitamente
o caminho para as extensões específicas do dispositivo com a opção -s
(ou
--device_specific
), que tem a prioridade máxima. Isso permite que você
corrija erros e faça mudanças nas extensões do releasetools e aplique essas mudanças a arquivos
de destino antigos.
Agora, quando você executa ota_from_target_files
, ele seleciona automaticamente o
módulo específico do dispositivo no arquivo .zip target_files e o usa ao gerar pacotes
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
Outra opção é especificar extensões específicas do dispositivo ao executar
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
Observação:para uma lista completa de opções, consulte os
comentários ota_from_target_files
em
build/make/tools/releasetools/ota_from_target_files
.
Mecanismo de sideload
A recuperação tem um mecanismo de sideload para instalar manualmente um pacote de atualização sem fazer o download dele por transmissão sem fio pelo sistema principal. O sideload é útil para depurar ou fazer mudanças em dispositivos em que o sistema principal não pode ser inicializado.
Historicamente, o sideloading era feito carregando pacotes do cartão SD do dispositivo. No caso de um dispositivo que não inicializa, o pacote pode ser colocado no cartão SD usando outro computador e, em seguida, o cartão SD inserido no dispositivo. Para acomodar dispositivos Android sem armazenamento externo removível, a recuperação oferece suporte a dois mecanismos adicionais de sideload: carregar pacotes da partição de cache e carregá-los por USB usando o adb.
Para invocar cada mecanismo de sideload, o método Device::InvokeMenuItem()
do dispositivo
pode retornar os seguintes valores de BuiltinAction:
-
APPLY_EXT. Transferir um pacote de atualização por sideload do armazenamento externo (diretório
/sdcard
). O recovery.fstab precisa definir o ponto de montagem/sdcard
. Isso não pode ser usado em dispositivos que emulam um cartão SD com um symlink para/data
(ou algum mecanismo semelhante). Normalmente,/data
não está disponível para recuperação porque pode estar criptografado. A interface de recuperação mostra um menu de arquivos .zip em/sdcard
e permite que o usuário selecione um. -
APPLY_CACHE. Semelhante ao carregamento de um pacote de
/sdcard
, exceto que o diretório/cache
(que está sempre disponível para recuperação) é usado. No sistema regular,/cache
só pode ser gravado por usuários privilegiados, e se o dispositivo não for inicializável, o diretório/cache
não poderá ser gravado de forma alguma, o que torna esse mecanismo de utilidade limitada. -
APPLY_ADB_SIDELOAD. Permite que o usuário envie um pacote para o dispositivo usando um cabo USB e
a ferramenta de desenvolvimento adb. Quando esse mecanismo é invocado, a recuperação inicia a própria miniversão do daemon adbd para permitir que o adb em um computador host conectado se comunique com ele. Esta mini versão só aceita um comando:
adb sideload filename
. O arquivo nomeado é enviado da máquina host para o dispositivo, que o verifica e instala como se estivesse no armazenamento local.
Algumas ressalvas:
- Há suporte apenas para transporte USB.
-
Se a recuperação executar o adbd normalmente (geralmente verdadeiro para builds userdebug e eng), ele será
desligado enquanto o dispositivo estiver no modo adb sideload e será reiniciado quando o adb
sideload terminar de receber um pacote. No modo de sideload do adb, nenhum comando do adb funciona, exceto
sideload
(logcat
,reboot
,push
,pull
,shell
etc. falham). -
Não é possível sair do modo de sideload do adb no dispositivo. Para cancelar, envie
/dev/null
(ou qualquer outra coisa que não seja um pacote válido) como o pacote. Assim, o dispositivo não vai conseguir verificar e vai interromper o procedimento de instalação. O métodoCheckKey()
da implementação do RecoveryUI continuará sendo chamado para pressionamentos de tecla. Assim, você pode fornecer uma sequência de teclas que reinicia o dispositivo e funciona no modo de sideload do adb.