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 baseband 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
A partir do Android 2.3, a plataforma oferece suporte a dispositivos flash eMMC e ao sistema de arquivos ext4 executado neles. Ele também oferece suporte a dispositivos flash de tecnologia de memória (MTD, na sigla em inglês) e ao sistema de arquivos yaffs2 de versões mais antigas.
O arquivo de mapa de partição é especificado por TARGET_RECOVERY_FSTAB. Esse arquivo é usado pelo binário de recuperação e pelas ferramentas de criação de pacotes. É possível especificar o nome do arquivo de mapa em TARGET_RECOVERY_FSTAB no BoardConfig.mk.
Um arquivo de mapa de partição de exemplo pode ter esta aparência:
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 sobre um dispositivo flash MTD. "device" precisa ser o nome da partição MTD
e precisa aparecer em
/proc/mtd
. - mtd
-
Uma partição MTD bruta, usada para partições inicializáveis, como inicialização e recuperação. A MTD não
é montada, 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 sobre 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. Semelhante ao tipo mtd, o eMMc nunca é montado, mas a string de ponto de montagem é usada para localizar o dispositivo na tabela.
- vfat
-
Um sistema de arquivos FAT em um dispositivo de bloco, geralmente para armazenamento externo, como um cartão SD. O
dispositivo é o dispositivo de bloco. O device2 é 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ções).
Todas as partições precisam ser montadas no diretório raiz (ou seja, o valor do ponto de montagem precisa começar com um caractere de barra e não pode ter outros caracteres de barra). Essa restrição se aplica apenas à montagem de sistemas de arquivos em recuperação. O sistema principal pode ser montado 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 de dados do usuário durante uma operação de limpeza de dados/redefinição de fábrica ou para a partição do sistema durante a instalação de um pacote OTA completo). Se o valor de comprimento for negativo, o tamanho para formatação será calculado somando o valor de comprimento ao tamanho real da partição. Por exemplo, a configuração "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 a criptografia da partição de dados do usuário, em que os metadados de criptografia são armazenados no final da partição que não pode ser substituída.
Observação:os campos device2 e options são opcionais, criando ambiguidade na análise. Se a entrada no quarto campo da linha começar com um caractere "/", ela será considerada uma entrada device2. Se a entrada não começar com um 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 no formato bootanimation.
Para dispositivos Android Things, faça upload do arquivo compactado no console do Android Things para incluir as imagens no produto selecionado.
Observação:essas imagens precisam atender às diretrizes da marca Android. Para conferir as diretrizes de marca, consulte a seção sobre o Android no Partner Marketing Hub.
Interface de recuperação
Oferecer suporte a dispositivos com hardwares diferentes (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 para 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 o dispositivo.
Observação:talvez apareça uma mensagem informando No Command aqui. Para alternar o texto, mantenha o botão liga/desliga pressionado enquanto pressiona o botão de aumentar o volume. Se o dispositivo não tiver os dois botões, toque e pressione 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"
Cabeçalho e funções de 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:linhas longas são truncadas (não são agrupadas), 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 tenha uma tela para que você possa herdar da implementação
integrada de ScreenRecoveryUI. Consulte as instruções para
dispositivos sem tela. A única função que pode
ser personalizada em ScreenRecoveryUI é CheckKey()
, que faz o processamento
inicial de chaves assíncronas:
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 independentemente do que estiver acontecendo no restante da recuperação: quando o menu está desativado, quando
está ativado, durante a instalação do pacote, durante a exclusão permanente de dados do usuário etc. Ele pode retornar uma das quatro
constantes:
- CHAVE. Ativar ou desativar a exibição do menu e/ou do registro de texto
- REBOOT. Reinicie o dispositivo imediatamente
- IGNORE. Ignorar esse pressionamento de tecla
- ENFILEIRAR. Enfileira essa tecla para ser consumida 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 sendo pressionadas. Na sequência de eventos principais acima, se CheckKey(B)
chamasse IsKeyPressed(A)
, ele retornaria verdadeiro.
CheckKey()
pode manter o estado na classe. Isso pode ser útil para detectar
sequências de chaves. Este exemplo mostra uma configuração um pouco mais complexa: a tela é alternada
pressionando e segurando o botão Liga/Desliga e pressionando o botão de aumentar volume. O dispositivo pode ser reiniciado imediatamente
pressionando 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 de animation_fps
na própria imagem. Em versões anteriores do
Android, era necessário definir animation_fps
.
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 QPS)
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 da RecoveryUI, defina a classe do dispositivo (subclasse da
classe integrada do dispositivo). Ela precisa criar uma única instância da classe de interface e retornar essa
instância 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 que qualquer ação tenha sido
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 .... }
Menu de recuperação de fornecimento e gerenciamento
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 recebe um código de chave (que foi processado e enfileirado pelo
método CheckKey()
do objeto de 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 será invocado imediatamente (consulte o
método InvokeMenuItem()
abaixo). Caso contrário, pode ser uma das constantes
predefinidas abaixo:
- 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 no momento
- 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 um trackball (que gera eventos de entrada com o tipo EV_REL
e o código REL_Y), a recuperação sintetiza pressionamentos de tecla KEY_UP e KEY_DOWN sempre que o
dispositivo de entrada semelhante a um trackball informar movimento no eixo Y. Basta mapear os eventos KEY_UP e
KEY_DOWN para ações de menu. Esse mapeamento não acontece para
CheckKey()
. Portanto, não é possível usar movimentos do trackball como gatilhos para reiniciar ou
alternar a tela.
Teclas modificadoras
Para verificar se as teclas estão sendo 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 inicia uma
exclusão permanente de dados, independentemente de o menu estar visível ou não. 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 consegue ver o destaque. No entanto, você pode retornar os valores, se quiser.
InvokeMenuItem
Em seguida, forneça um método InvokeMenuItem()
que mapeie posições de inteiros na matriz
de itens retornados por GetMenuItems()
para ações. Para a matriz de itens no
exemplo de 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 do tipo enumerado BuiltinAction para informar ao sistema que ele precisa realizar essa ação (ou o membro NO_ACTION se você quiser que o sistema não faça nada). Esse é o lugar para fornecer mais funcionalidades de recuperação além do que está no sistema: adicione um item para ele no menu, execute-o aqui quando o 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.
- REBOOT. 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 Sideload.
- WIPE_CACHE. Reformate apenas a partição de cache. Nenhuma confirmação é necessária, porque isso é relativamente inofensivo.
- WIPE_DATA. Reformate as partições de dados do usuário e de cache, também conhecidas como redefinição para configuração original. 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 eliminação
de dados é iniciada (seja 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 apagadas. Se o dispositivo armazenar dados do usuário em qualquer lugar que não seja essas duas
partições, eles serão apagados aqui. Você precisa retornar 0 para indicar sucesso e outro
valor para falha, embora o valor de retorno seja ignorado no momento. As partições de dados do usuário e de cache
são apagadas, independentemente de você retornar sucesso ou falha.
int WipeData() { // ... do something tardis-specific here, if needed .... return 0; }
Dispositivo de fabricação
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 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 dispositivo. No 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 do sistema para esse dispositivo, especifique a 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 para recuperação, preenche a barra de progresso da instalação e reinicializa para o novo sistema sem a entrada do usuário. Em caso de problemas com a atualização do sistema, a única ação que o usuário pode realizar é ligar para 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 versões mais recentes
A interface de recuperação do Android 5.0 e versões mais recentes usa duas imagens principais: a imagem error e a animação installing.
![]() 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çada por linha (é por isso que a Figura 2 aparece comprimida). Por exemplo, para uma
animação de sete frames de 200 x 200, crie uma única imagem de 200 x 1400, em que o primeiro frame é as linhas 0, 7,
14, 21, ...; o segundo frame é as linhas 1, 8, 15, 22, ...; e assim por diante. A imagem combinada inclui um
bloco de texto que indica o número de frames de animação e o número de frames por segundo
(FPS). A ferramenta bootable/recovery/interlace-frames.py
pega 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, é 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 error (mostrada acima) e a animação installing, além de várias imagens de sobreposição:
![]() Figura 3.icon_installing.png |
![]() Figura 4.icon-installing_overlay01.png |
![]() Figura 5.icon_installing_overlay07.png |
Durante a instalação, a tela é construída desenhando a imagem icon_installing.png e, em seguida, um dos frames de sobreposição na posição correta. Aqui, uma caixa vermelha é sobreposta para destacar onde a sobreposição é colocada sobre a imagem de base:
![]() Figura 6. Instalando o frame de animação 1 (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 seguintes são mostrados desenhando apenas a próxima imagem de sobreposição em cima do 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 de imagens padrão, substitua o método Init()
na
subclasse para mudar esses valores para suas 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, é sempre uma imagem estática.
Texto de recuperação localizado
O Android 5.x mostra uma string de texto (por exemplo, "Installing system update...") 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 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 a atualização do sistema…
- Erro!
- Limpando… (ao fazer uma limpeza de dados/redefinição para a configuração original)
- Nenhum comando (quando um usuário inicia a recuperação manualmente)
O app Android em bootable/recovery/tools/recovery_l10n/
renderiza as localizações
de uma mensagem e cria a imagem composta. Para saber mais 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 principal (ou animação). 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 preenchida é exibida ao lado da extremidade direita da imagem vazia para criar a barra de progresso. A posição da fronteira entre as duas imagens é alterada 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%
É possível fornecer versões específicas do dispositivo dessas imagens colocando-as em device/yoyodyne/tardis/recovery/res/images
(neste exemplo). 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 no formato RGB ou
RGBA com profundidade de cor de 8 bits são aceitos.
Observação:no Android 5.x, se a localidade for conhecida para recuperação e for um idioma da direita para a esquerda (RTL, na sigla em inglês) (árabe, hebraico etc.), a barra de progresso será preenchida da direita para a esquerda.
Dispositivos sem telas
Nem todos os dispositivos Android têm telas. Se o dispositivo for um aparelho sem cabeça ou tiver uma interface somente de áudio, talvez seja necessário fazer uma personalização mais extensa da interface de recuperação. Em vez de criar uma subclasse de ScreenRecoveryUI, subclassifique diretamente a classe mãe RecoveryUI.
A RecoveryUI tem métodos para processar operações de IU de nível inferior, como "alternar a tela",
"atualizar a barra de progresso", "mostrar o menu", "mudar a seleção do menu" etc. É possível substituir
esses métodos para fornecer uma interface adequada para seu dispositivo. Talvez seu dispositivo tenha LEDs em que
você pode usar cores ou padrões de piscar diferentes para indicar o estado ou reproduzir
á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 de CheckKey()
e
HandleMenuKey()
que nunca ativam a exibição ou selecionam um item
de menu. Nesse caso, muitos dos métodos RecoveryUI que você precisa fornecer podem ser stubs
vazios.
Consulte bootable/recovery/ui.h
para ver a declaração de RecoveryUI e saber quais métodos
você precisa oferecer suporte. A RecoveryUI é abstrata, alguns métodos são totalmente virtuais e precisam ser fornecidos por
subclasses, mas ela contém o código para processar entradas de chaves. Você também pode substituir esse
valor se o dispositivo não tiver chaves ou se quiser processá-las de maneira diferente.
Updater
É possível usar o 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 foram avaliados no momento em que a função foi 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á segurando e retorne NULL imediatamente. Isso
propaga os abortos para a pilha 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; }
A verificação de NULL e a liberação de argumentos avaliados anteriormente podem se tornar tediosas para vários
argumentos. A função ReadValueArgs()
pode facilitar isso. Em vez do código
acima, você poderia ter escrito o seguinte:
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()
não faz a verificação de tipo, então você precisa fazer isso aqui. É mais
conveniente fazer isso com uma instrução if, mas isso gera uma mensagem de erro menos
específica quando ela falha. No entanto, ReadValueArgs()
processa a avaliação
de cada argumento e libera todos os argumentos avaliados anteriormente (bem como define 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á transmitida ao
autor da chamada. O autor da chamada assume a propriedade de todos os dados apontados por
Value*
, especificamente o membro de dados.
Neste caso, você quer retornar um valor verdadeiro ou falso para indicar sucesso. Lembre-se da
convenção de que a string vazia é false e todas as outras strings são true. Você
precisa mallocar um objeto Value com uma cópia malloc'd da string constante a ser retornada, já que o
autor da chamada vai free()
ambos. Não se esqueça de chamar FreeValue()
nos
objetos que você recebeu avaliando 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()
envolve uma string em um novo objeto Value.
Use para escrever o código acima de forma mais sucinta:
FreeValue(key); FreeValue(image); return StringValue(strdup(successful ? "t" : "")); }
Para vincular funções ao interpretador 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 as funções específicas do dispositivo como device.whatever
para evitar
conflitos com funções integradas futuras.
void Register_librecovery_updater_tardis() { RegisterFunction("tardis.reprogram", ReprogramTardisFn); }
Agora você pode configurar o makefile para criar uma biblioteca estática com seu código. Esse é 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 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 a biblioteca. Adicione sua biblioteca a
TARGET_RECOVERY_UPDATER_LIBS, que pode conter várias bibliotecas, todas registradas.
Se o código depender de outras bibliotecas estáticas que não são extensões do Edify (ou seja,
eles não têm uma função Register_libname
), é possível listá-los em
TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vinculá-los ao atualizador sem chamar a
função de registro (inexistente). Por exemplo, se o código específico do dispositivo quiser usar
zlib para descompactar dados, inclua 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 pacote OTA agora podem chamar a 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 para a nova função de extensão.
Geração de pacotes OTA
O componente final é fazer com que as ferramentas de geração de pacotes OTA saibam sobre seus dados específicos do dispositivo e emitam scripts de atualização que incluem chamadas para suas funções de extensão.
Primeiro, faça com que o sistema de build saiba sobre um blob de dados específico do dispositivo. Supondo que seu arquivo
de dados esteja em device/yoyodyne/tardis/tardis.dat
, declare o seguinte no
AndroidBoard.mk do dispositivo:
device/yoyodyne/tardis/AndroidBoard.mk
[...] $(call add-radio-file,tardis.dat)
Você também pode colocá-lo em um Android.mk, mas ele precisa ser protegido por uma verificação do dispositivo, já que todos os arquivos Android.mk na árvore são carregados, não importa qual dispositivo está sendo criado. Se a árvore incluir vários dispositivos, você só vai querer que o arquivo tardis.dat seja 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. Eles podem não ter nada a ver com o
rádio do dispositivo (se houver). Eles são simplesmente blobs opacos de dados que o sistema de build copia para
os arquivos de destino .zip usados pelas ferramentas de geração de OTA. Quando você faz um build, o tardis.dat é
armazenado no target-files.zip como RADIO/tardis.dat
. É possível 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 Python (com o nome 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 o caso de geração de um pacote OTA incremental. Neste exemplo, suponha que você precise reprogramar os atrasos 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
É possível fornecer as seguintes funções no módulo (implemente apenas as necessárias).
FullOTA_Assertions()
- Chamado perto do início da geração de uma OTA completa. Este é 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 são transmitidas, mas antes que qualquer mudança seja feita. É possível emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
FullOTA_InstallEnd()
- 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.
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 são transmitidas, mas antes que qualquer mudança seja feita. É possível emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
IncrementalOTA_VerifyEnd()
- Chamado no 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 ponto, nada no dispositivo foi alterado. Também é possível emitir um 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 anterior esperado, mas antes de qualquer mudança. É possível emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
IncrementalOTA_InstallEnd()
- Semelhante à contraparte do pacote OTA completo, ele é 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 são emitidos. Também é possível emitir outros comandos para atualizações específicas do dispositivo.
Observação:se o dispositivo perder energia, a instalação OTA poderá ser reiniciada do início. Esteja preparado para lidar com dispositivos em que esses comandos já foram executados, totalmente ou parcialmente.
Transmitir funções para objetos de informações
Transmita funções para um único objeto de informações que contém vários itens úteis:
-
info.input_zip. (Somente OTAs completos) O objeto
zipfile.ZipFile
para o .zip de arquivos de destino de entrada. -
info.source_zip. (Somente OTAs incrementais) O objeto
zipfile.ZipFile
para os arquivos de destino de origem .zip (o build já está 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 que ele não seja executado em comandos emitidos depois.
Para saber mais sobre o objeto de informações, consulte a documentação da Fundação Software Python 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 de arquivos de destino
META/releasetools.py
.
Ao executar as ferramentas de lançamento (img_from_target_files
ou
ota_from_target_files
), o script releasetools.py nos arquivos de destino .zip, se
presente, é preferido em relação ao da árvore 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 maior prioridade. Isso permite corrigir erros e fazer alterações nas extensões do releasetools e aplicar essas mudanças aos arquivos de destino antigos.
Agora, quando você executa ota_from_target_files
, ele seleciona automaticamente o
módulo específico do dispositivo do 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
Como alternativa, você pode 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 conferir 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 pelo sistema principal. O sideload é útil para depurar ou fazer alterações em dispositivos em que o sistema principal não pode ser inicializado.
Historicamente, o sideload 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: carregando pacotes da partição de cache e carregando-os por USB usando adb.
Para invocar cada mecanismo de sideload, o método Device::InvokeMenuItem()
do dispositivo
pode retornar os seguintes valores de BuiltinAction:
-
APPLY_EXT. Faça o sideload de um pacote de atualização do armazenamento externo (diretório
/sdcard
). O recovery.fstab precisa definir o ponto de montagem/sdcard
. Ele não pode ser usado em dispositivos que emulam um cartão SD com um link simbólico para/data
(ou algum mecanismo semelhante). O/data
normalmente 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 em vez disso. No sistema regular, o/cache
só pode ser gravado por usuários privilegiados, e, se o dispositivo não puder ser inicializado, o diretório/cache
não poderá ser gravado (o que limita o uso desse mecanismo). -
APPLY_ADB_SIDELOAD. Permite que o usuário envie um pacote para o dispositivo por um cabo USB e
a ferramenta de desenvolvimento adb. Quando esse mecanismo é invocado, a recuperação inicia a própria
versão do daemon adbd para permitir que o adb em um computador host conectado se comunique com ele. Essa versão
mínima oferece suporte a apenas 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:
- Somente o transporte USB é aceito.
-
Se a recuperação executar o adbd normalmente (geralmente verdadeiro para builds userdebug e eng), ele
será encerrado enquanto o dispositivo estiver no modo de sideload do adb e será reiniciado quando o sideload
do adb terminar de receber um pacote. Enquanto estiver no modo de sideload do adb, nenhum comando do adb, exceto
sideload
, vai funcionar (logcat
,reboot
,push
,pull
,shell
etc.). -
Não é possível sair do modo de sideload do adb no dispositivo. Para abortar, envie
/dev/null
(ou qualquer outra coisa que não seja um pacote válido) como o pacote. Em seguida, o dispositivo não conseguirá verificar o pacote e interromperá o procedimento de instalação. O métodoCheckKey()
da implementação da RecoveryUI continuará sendo chamado para pressionamentos de tecla, para que você possa fornecer uma sequência de teclas que reinicie o dispositivo e funcione no modo de sideload do adb.