Código específico do dispositivo

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();
}

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.

imagem mostrada durante o erro de OTA

Figura 1. icon_error.png

imagem mostrada durante a instalação do OTA

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:

imagem mostrada durante a instalação do OTA

Figura 3.icon_installing.png

imagem mostrada como a primeira
sobreposição

Figura 4.icon-installing_overlay01.png

imagem mostrada como sétima
sobreposição

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:

imagem composta de
instalação e primeira sobreposição

Figura 6. Instalando o frame de animação 1 (icon_installing.png + icon_installing_overlay01.png)

imagem composta da
instalação e da sétima sobreposição

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:

imagem do 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:

Barra de progresso vazia

Figura 9.progress_empty.png

barra de progresso completa

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:

Barra de progresso em 1%

Figura 11. Barra de progresso em 1%>

Barra de progresso em 10%

Figura 12. Barra de progresso em 10%

barra de progresso em 50%

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étodo CheckKey() 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.