Código específico do dispositivo

O sistema de recuperação inclui vários ganchos para inserir código específico do dispositivo para que as atualizações OTA também possam atualizar partes do dispositivo além do 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

A partir do Android 2.3, a plataforma suporta dispositivos flash eMMc e o sistema de arquivos ext4 executado nesses dispositivos. Ele também suporta dispositivos flash Memory Technology Device (MTD) e o 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. Você pode especificar o nome do arquivo de mapa em TARGET_RECOVERY_FSTAB em BoardConfig.mk.

Um arquivo de mapa de partição de amostra 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 devem ser definidos (os dispositivos também podem adicionar partições extras). Existem cinco tipos de sistema de arquivos suportados:

yaffs2
Um sistema de arquivos yaffs2 no topo de um dispositivo flash MTD. "dispositivo" deve ser o nome da partição MTD e deve 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 está realmente montado, mas o ponto de montagem é usado como uma chave para localizar a partição. "dispositivo" deve ser o nome da partição MTD em /proc/mtd .
ext4
Um sistema de arquivos ext4 no topo de um dispositivo flash eMMc. "dispositivo" deve 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 é realmente montado, mas a string do ponto de montagem é usada para localizar o dispositivo na tabela.
vfat
Um sistema de arquivos FAT sobre um dispositivo de bloco, geralmente para armazenamento externo, como um cartão SD. O dispositivo é o dispositivo de bloco; 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ção).

Todas as partições devem ser montadas no diretório raiz (ou seja, o valor do ponto de montagem deve começar com uma barra e não ter outras barras). Essa restrição se aplica apenas à montagem de sistemas de arquivos em recuperação; o sistema principal é livre para montá-los em qualquer lugar. Os diretórios /boot , /recovery e /misc devem ser tipos brutos (mtd ou emmc), enquanto os diretórios /system , /data , /cache e /sdcard (se disponível) devem 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, opções . Atualmente, 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 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 "comprimento=-16384" significa que os últimos 16k dessa partição não serão substituídos quando essa partição for reformatada. Isso oferece suporte a recursos como criptografia da partição de dados do usuário (onde os metadados de criptografia são armazenados no final da partição que não devem ser substituídos).

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 de opções .

Animação de inicialização

Os fabricantes de dispositivos podem personalizar a animação exibida quando um dispositivo Android está inicializando. Para isso, construa um arquivo .zip organizado e localizado conforme as especificações no formato bootanimation .

Para dispositivos Android Things , você pode carregar o arquivo compactado no console do Android Things para incluir as imagens no produto selecionado.

Observação: essas imagens devem atender às diretrizes da marca Android .

IU de recuperação

Para oferecer suporte a dispositivos com diferentes hardwares disponíveis (botões físicos, LEDs, telas, etc.), você pode personalizar a interface de recuperação para exibir o status e acessar os recursos ocultos operados manualmente para cada dispositivo.

Seu objetivo é construir 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 escrever uma versão desse arquivo para o seu dispositivo.

Observação: você pode ver uma mensagem dizendo Nenhum comando aqui. Para alternar o texto, segure o botão liga / desliga enquanto pressiona o botão de aumentar o volume. Se seus dispositivos não tiverem os dois botões, pressione e segure 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 requer 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 alterar/selecionar o item realçado).

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 agrupadas), portanto, lembre-se da largura da tela do dispositivo.

Personalizando CheckKey

Em seguida, defina a implementação RecoveryUI do seu dispositivo. Este exemplo assume que o dispositivo tardis tem uma tela, portanto, você pode herdar da implementação ScreenRecoveryUI integrada (consulte as instruções para dispositivos sem tela CheckKey() .

class TardisUI : public ScreenRecoveryUI {
  public:
    virtual KeyAction CheckKey(int key) {
        if (key == KEY_HOME) {
            return TOGGLE;
        }
        return ENQUEUE;
    }
};

constantes CHAVE

As constantes KEY_* são definidas em linux/input.h . CheckKey() é chamado independentemente do que está acontecendo no restante da recuperação: quando o menu é desativado, quando é ativado, durante a instalação do pacote, durante a limpeza dos dados do usuário, etc. Ele pode retornar uma das quatro constantes:

  • ALTERNAR . Ativar ou desativar a exibição do menu e/ou log de texto
  • REINICIAR . Reinicie imediatamente o dispositivo
  • IGNORAR . Ignorar este pressionamento de tecla
  • ENQUEÇA . Enfileirar este pressionamento de tecla para ser consumido de forma síncrona (ou seja, pelo sistema de menu de recuperação se a exibição estiver habilitada)

CheckKey() é chamado sempre que um evento de tecla pressionada é seguido por um evento de tecla pressionada para a mesma tecla. (A sequência de eventos A-baixo B-baixo B-cima A-cima 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 de tecla acima, se CheckKey(B) chamado IsKeyPressed(A) , ele retornaria verdadeiro.)

CheckKey() pode manter o estado em sua classe; isso pode ser útil para detectar sequências de teclas. Este exemplo mostra uma configuração um pouco mais complexa: a exibição é alternada mantendo-se pressionado o botão liga/desliga e aumentando o volume, e o dispositivo pode ser reinicializado imediatamente pressionando o botão liga/desliga cinco vezes seguidas (sem outras teclas intervenientes):

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, você pode definir a variável animation_fps para controlar a velocidade em quadros por segundo (FPS) das animações.

Observação: o script interlace-frames.py atual permite que você armazene as informações animation_fps na própria imagem. Nas versões anteriores do Android, era necessário definir animation_fps você mesmo.

Para definir a variável animation_fps , substitua a função ScreenRecoveryUI::Init() em sua subclasse. Defina o valor e, em seguida, 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, você não precisa fornecer uma função Init() . Para obter detalhes sobre imagens, consulte Recovery UI Images .

Classe de dispositivo

Depois de ter uma implementação RecoveryUI, defina sua classe de dispositivo (uma subclasse da classe de dispositivo integrada). Ele deve criar uma única instância de sua classe de interface do usuário e retorná-la da função GetUI() :

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

Iniciar a recuperação

O método StartRecovery() é chamado no início da recuperação, depois que a interface do usuário foi inicializada e depois que os argumentos foram analisados, mas antes de qualquer ação ser executada. A implementação padrão não faz nada, então você não precisa fornecer isso em sua subclasse se não tiver nada para fazer:

   void StartRecovery() {
       // ... do something tardis-specific here, if needed ....
    }

Fornecendo e gerenciando o menu de recuperação

O sistema chama dois métodos para obter a lista de linhas de cabeçalho e a lista de itens. Nesta implementação, ele retorna os arrays estáticos definidos na parte superior 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 um pressionamento de tecla e a visibilidade do menu atual e decide qual ação tomar:

   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-chave (que foi previamente processado e enfileirado pelo método CheckKey() do objeto de interface do usuário) e o estado atual da visibilidade do log de menu/texto. O valor de retorno é um número inteiro. Se o valor for 0 ou superior, isso é considerado como 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 . Mova o destaque do menu para o item anterior
  • kRealçarBaixo . Mova o destaque do menu para o próximo item
  • kInvokeItem . Chamar o item atualmente destacado
  • kNoAction . Não faça nada com este pressionamento de tecla

Conforme implícito no argumento visible, HandleMenuKey() é chamado mesmo se o menu não estiver visível. Ao contrário 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á ociosa e aguardando entrada.

Mecanismos de trackball

Se o seu dispositivo tiver um mecanismo de entrada semelhante a um trackball (gera eventos de entrada com tipo EV_REL e código REL_Y), a recuperação sintetiza os pressionamentos de tecla KEY_UP e KEY_DOWN sempre que o dispositivo de entrada semelhante a um trackball relatar movimento no eixo Y. Tudo o que você precisa fazer é mapear os eventos KEY_UP e KEY_DOWN nas ações do menu. Esse mapeamento não acontece para CheckKey() , portanto, você não pode usar os movimentos do trackball como acionadores para reiniciar ou alternar a exibição.

Teclas modificadoras

Para verificar se as teclas estão sendo pressionadas como modificadores, chame o método IsKeyPressed() de seu próprio objeto de interface do usuário. Por exemplo, em alguns dispositivos, pressionar Alt-W na recuperação iniciaria uma limpeza de dados, independentemente de o menu estar visível ou não. Você poderia 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
        }
        ...
    }

Nota: Se visible for falso, não faz sentido retornar os valores especiais que manipulam o menu (mover destaque, invocar item destacado), pois o usuário não pode ver o destaque. No entanto, você pode retornar os valores, se desejar.

InvokeMenuItem

Em seguida, forneça um método InvokeMenuItem() que mapeia 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;
        }
    }

Este método pode retornar qualquer membro da enumeração BuiltinAction para instruir o sistema a executar essa ação (ou o membro NO_ACTION se você quiser que o sistema não faça nada). Este é o lugar para fornecer funcionalidade de recuperação adicional além do que está no sistema: adicione um item para ele em seu menu, execute-o aqui quando esse item de menu for chamado e retorne NO_ACTION para que o sistema não faça mais nada.

BuiltinAction contém os seguintes valores:

  • NO_ACTION . 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 obter detalhes, consulte Sideload .
  • WIPE_CACHE . Reformate apenas a partição de cache. Nenhuma confirmação necessária, pois isso é relativamente inofensivo.
  • WIPE_DATA . Reformate as partições de dados do usuário e cache, também conhecidas como redefinição de dados de fábrica. O usuário é solicitado a confirmar esta ação antes de prosseguir.

O último método, WipeData() , é opcional e é chamado sempre que uma operação de limpeza de dados é iniciada (a partir da recuperação por meio do menu ou quando o usuário optou por redefinir os 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 seu dispositivo armazenar dados do usuário em qualquer outro lugar além dessas duas partições, você deve apagá-los aqui. Você deve retornar 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, quer você retorne com sucesso ou com falha.

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

Fazer dispositivo

Por fim, inclua alguns clichês no final do arquivo recovery_ui.cpp para a função make_device() que cria e retorna uma instância de sua 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-o e vincule-o à recuperação em seu 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 da placa para este 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 de IU 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 do usuário: durante uma atualização normal, o telefone inicializa em recuperação, preenche a barra de progresso da instalação e inicializa de volta no novo sistema sem entrada do usuário. No caso de um problema de atualização do sistema, a única ação que o usuário pode tomar é 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 exibir uma sequência de texto (por exemplo, "Instalando atualização do sistema...") junto com a imagem. Para obter detalhes, consulte Texto de recuperação localizado .

Android 5.0 e posterior

A IU de recuperação do Android 5.0 e posterior usa duas imagens principais: a imagem de erro e a animação de instalação .

imagem mostrada durante o erro ota

Figura 1. icon_error.png

imagem mostrada durante a instalação ota

Figura 2. icon_installing.png

A animação de instalação é representada como uma única imagem PNG com diferentes quadros da animação entrelaçados por linha (é por isso que a Figura 2 aparece compactada). Por exemplo, para uma animação de sete quadros de 200x200, crie uma única imagem de 200x1400 onde o primeiro quadro é as linhas 0, 7, 14, 21, ...; o segundo quadro é as linhas 1, 8, 15, 22, ...; etc. A imagem combinada inclui um pedaço de texto que indica o número de quadros de animação e o número de quadros por segundo (FPS). A ferramenta bootable/recovery/interlace-frames.py pega um conjunto de quadros 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, você só precisa fornecer a imagem icon_installing.png e definir o número de quadros na animação para 0 (o ícone de erro não é animado; é sempre uma imagem estática).

Android 4.x e anteriores

A IU de recuperação do Android 4.xe 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:

imagem mostrada durante a instalação ota

Figura 3. icon_installing.png

imagem mostrada como 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 exibição na tela é construída desenhando a imagem icon_installing.png e, em seguida, desenhando um dos quadros de sobreposição sobre ela no deslocamento adequado. Aqui, uma caixa vermelha é sobreposta para destacar onde a sobreposição é colocada na parte superior da imagem base:

imagem composta da instalação mais a primeira sobreposição

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

imagem composta da instalação mais a sétima sobreposição

Figura 7. Instalando o quadro de animação 7 (icon_installing.png + icon_installing_overlay07.png)

Os quadros subseqüentes são exibidos desenhando apenas a próxima imagem de sobreposição sobre o que já está lá; a imagem base não é redesenhada.

O número de quadros 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() em sua subclasse para alterar esses valores para suas imagens personalizadas (para obter detalhes, consulte ScreenRecoveryUI ). O script bootable/recovery/make-overlay.py pode ajudar na conversão de um conjunto de quadros de imagem para a forma "imagem base + imagens de sobreposição" necessária para a recuperação, incluindo a computação 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, você só precisa fornecer a imagem icon_installing.png e definir o número de quadros na animação para 0 (o ícone de erro não é animado; é sempre uma imagem estática).

Texto de recuperação localizado

O Android 5.x exibe uma sequência de texto (por exemplo, "Instalando atualização do sistema...") junto com a imagem. Quando o sistema principal inicia a recuperação, ele passa 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 sequências 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 exibir as seguintes mensagens:

  • Instalando atualização do sistema...
  • Erro!
  • Apagando... (ao fazer uma limpeza de dados/redefinição de fábrica)
  • Nenhum comando (quando um usuário inicia a recuperação manualmente)

O aplicativo Android em bootable/recovery/tools/recovery_l10n/ renderiza as localizações de uma mensagem e cria a imagem composta. Para obter detalhes sobre como usar este aplicativo, consulte os comentários em bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java .

Quando um usuário inicia a recuperação manualmente, a localidade pode não estar disponível e nenhum texto é exibido. Não torne as mensagens de texto críticas para o processo de recuperação.

Nota: A interface oculta que exibe mensagens de log e permite ao usuário selecionar 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 devem ser do 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 próxima à extremidade direita da imagem vazia para criar a barra de progresso. A posição do limite entre as duas imagens é alterada para indicar o progresso. Por exemplo, com os pares de imagens de entrada acima, exiba:

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%

Você pode fornecer versões específicas do dispositivo dessas imagens colocando-as (neste exemplo) em device/yoyodyne/tardis/recovery/res/images . Os nomes dos arquivos devem corresponder aos listados acima; quando um arquivo é encontrado nesse diretório, o sistema de compilação 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 suportados.

Observação: no Android 5.x, se a localidade for conhecida para recuperação e for um idioma da direita para a esquerda (RTL) (á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 seu dispositivo for um dispositivo headless ou tiver uma interface somente de áudio, talvez seja necessário fazer uma personalização mais ampla da interface do usuário de recuperação. Em vez de criar uma subclasse de ScreenRecoveryUI, crie uma subclasse de sua classe pai RecoveryUI diretamente.

RecoveryUI tem métodos para lidar com operações de interface do usuário de nível inferior, como "alternar a exibição", "atualizar a barra de progresso", "mostrar o menu", "alterar a seleção do menu" etc. Você pode substituí-los para fornecer uma interface apropriada para o seu dispositivo. Talvez seu dispositivo tenha LEDs onde você pode usar diferentes cores ou padrões de piscar para indicar o estado, ou talvez você possa reproduzir áudio. (Talvez você não queira oferecer suporte a um menu ou ao modo de "exibição de texto"; você pode impedir o acesso a eles com implementações 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 apenas stubs vazios.)

Veja bootable/recovery/ui.h para a declaração de RecoveryUI para ver quais métodos você deve suportar. RecoveryUI é abstrato - alguns métodos são puramente virtuais e devem ser fornecidos por subclasses - mas contém o código para fazer o processamento das principais entradas. Você também pode substituir isso, se o seu dispositivo não tiver chaves ou se quiser processá-las de maneira diferente.

Atualizador

Você pode 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 de dentro do script do atualizador. Aqui está uma função de exemplo para o dispositivo tardis:

device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h>
#include <string.h>

#include "edify/expr.h"

Cada função de extensão tem 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* representando 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 sua função é chamada — a lógica de sua função determina quais deles são avaliados e quantas vezes. Assim, você pode 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, você deve liberar todos os recursos que está segurando e imediatamente retornar NULL (isso propaga aborts até a pilha do Edify). Caso contrário, você se apropria do Value retornado e é responsável por eventualmente 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 como este:

   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 ser tedioso para vários argumentos. A função ReadValueArgs() pode facilitar isso. Em vez do código acima, você poderia ter escrito isso:

   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 verificação de tipo, então você deve fazer isso aqui; é mais conveniente fazer isso com uma instrução if ao custo de produzir uma mensagem de erro um pouco menos específica quando ela falhar. Mas ReadValueArgs() lida com a avaliação de cada argumento e libera todos os argumentos avaliados anteriormente (além de definir uma mensagem de erro útil) se qualquer uma das avaliações falhar. Você pode 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 deve ser um objeto Value* ; a propriedade deste objeto passará para o chamador. O chamador assume a propriedade de todos os dados apontados por este Value* — especificamente o datamember.

Nesse caso, você deseja retornar um valor verdadeiro ou falso para indicar o sucesso. Lembre-se da convenção de que a string vazia é falsa e todas as outras strings são verdadeiras . Você deve malloc um objeto Value com uma cópia malloc'd da string constante a ser retornada, já que o chamador irá free() ambos. Não se esqueça de chamar FreeValue() nos objetos que você obteve 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() agrupa 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 conectar funções ao interpretador edify, forneça a função Register_ foo onde foo é o nome da biblioteca estática que contém este código. Chame RegisterFunction() para registrar cada função de extensão. Por convenção, nomeie as funções específicas device . whatever para evitar conflitos com futuras funções integradas adicionadas.

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. (Este é o mesmo makefile usado para personalizar a interface do usuário de recuperação na seção anterior; seu dispositivo pode ter ambas as 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 deve corresponder ao nome da função Register_ libname contida nela.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Por fim, configure a compilação de recuperação para extrair sua biblioteca. Adicione sua biblioteca a TARGET_RECOVERY_UPDATER_LIBS (que pode conter várias bibliotecas; todas elas são registradas). Se o seu código depende de outras bibliotecas estáticas que não são extensões do edify (ou seja, elas não têm uma função Register_ libname ), você pode listá-las em TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vinculá-las ao atualizador sem chamar sua função de registro (inexistente). Por exemplo, se o código específico do seu 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 em seu pacote OTA agora podem chamar sua função como qualquer outra. Para reprogramar seu 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 interna 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 pacote OTA

O componente final é fazer com que as ferramentas de geração de pacotes OTA conheçam os dados específicos do seu dispositivo e emitam scripts atualizadores que incluem chamadas para suas funções de extensão.

Primeiro, informe o sistema de compilação 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 seu dispositivo:

device/yoyodyne/tardis/AndroidBoard.mk
  [...]

$(call add-radio-file,tardis.dat)

Você também pode colocá-lo em um Android.mk, mas ele deve ser protegido por uma verificação de dispositivo, pois todos os arquivos Android.mk na árvore são carregados, independentemente do dispositivo que está sendo construído. (Se sua árvore incluir vários dispositivos, você deseja adicionar apenas o arquivo tardis.dat 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

Estes são chamados de arquivos de rádio por razões históricas; 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 construção copia para os arquivos de destino .zip usados ​​pelas ferramentas de geração OTA. Quando você faz uma compilaçã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 Python (deve ser denominado releasetools.py) que as ferramentas possam chamar, se 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 lida com o caso de geração de um pacote OTA incremental. Para este exemplo, suponha que você precise reprogramar o tardis somente quando o arquivo tardis.dat for alterado entre duas compilações.

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 necessárias).

FullOTA_Assertions()
Chamado perto do início da geração de uma OTA completa. Este é um bom lugar para emitir afirmações sobre o estado atual do dispositivo. Não emita comandos de script que façam alterações no dispositivo.
FullOTA_InstallBegin()
Chamado depois que todas as afirmações sobre o estado do dispositivo foram aprovadas, mas antes que qualquer alteração tenha sido feita. Você pode emitir comandos para atualizações específicas do dispositivo que devem ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
FullOTA_InstallEnd()
Chamado ao final da geração do script, após a emissão dos comandos de script para atualizar as partições de boot e de sistema. Você também pode emitir comandos adicionais 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 que qualquer alteração tenha sido feita. Você pode emitir comandos para atualizações específicas do dispositivo que devem ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
IncrementalOTA_VerifyEnd()
Chamado no final da fase de verificação, quando o script terminar de confirmar se os arquivos que ele vai tocar têm o conteúdo inicial esperado. Neste ponto, nada no dispositivo foi alterado. Você também pode emitir código para verificações adicionais específicas do dispositivo.
IncrementalOTA_InstallBegin()
Chamado depois que os arquivos a serem corrigidos foram verificados como tendo o estado anterior esperado, mas antes de qualquer alteração ser feita. Você pode emitir comandos para atualizações específicas do dispositivo que devem ser executadas antes que qualquer outra coisa no dispositivo seja alterada.
IncrementalOTA_InstallEnd()
Semelhante ao pacote OTA completo, ele é chamado no final da geração do script, após a emissão dos comandos de script para atualizar as partições de inicialização e do sistema. Você também pode emitir comandos adicionais para atualizações específicas do dispositivo.

Nota: Se o dispositivo ficar sem energia, a instalação OTA pode reiniciar desde o início. Esteja preparado para lidar com dispositivos nos quais esses comandos já foram executados, total ou parcialmente.

Passar funções para objetos de informação

Passe funções para um único objeto de informação que contém vários itens úteis:

  • info.input_zip . (Somente OTAs completos) O objeto zipfile.ZipFile para os arquivos de destino de entrada .zip.
  • info.source_zip . (Somente OTAs incrementais) O objeto zipfile.ZipFile para os arquivos de destino de origem .zip (a compilação já no dispositivo quando o pacote incremental está sendo instalado).
  • info.target_zip . (Somente OTAs incrementais) O objeto zipfile.ZipFile para os arquivos-alvo .zip de destino (a compilação 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 enviar texto para o script. Certifique-se de que o texto de saída termine com um ponto e vírgula para que não seja executado em comandos emitidos posteriormente.

Para obter detalhes sobre o objeto info, consulte a documentação do Python Software Foundation para arquivos ZIP .

Especifique a localização do módulo

Especifique a localização do script releasetools.py do seu dispositivo em seu arquivo BoardConfig.mk:

device/yoyodyne/tardis/BoardConfig.mk
 [...]

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Se TARGET_RELEASETOOLS_EXTENSIONS não for definido, o padrão será o diretório $(TARGET_DEVICE_DIR)/../common ( device/yoyodyne/common neste exemplo). É melhor definir explicitamente a localização do script releasetools.py. Ao construir o dispositivo tardis, o script releasetools.py é incluído no arquivo target-files .zip ( 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 target-files .zip, se presente, é preferido em relação ao da árvore de origem do Android. Você também pode especificar explicitamente o caminho para as extensões específicas do dispositivo com a opção -s (ou --device_specific ), que tem prioridade máxima. Isso permite que você corrija erros e faça alterações nas extensões do releasetools e aplique essas alterações a arquivos de destino antigos.

Agora, quando você executa ota_from_target_files , ele seleciona automaticamente o módulo específico do dispositivo do arquivo target_files .zip 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 obter uma lista completa de opções, consulte os comentários ota_from_target_files em build/make/tools/releasetools/ota_from_target_files .

Carregamento lateral

A recuperação tem um mecanismo de sideload para instalar manualmente um pacote de atualização sem baixá-lo pelo ar pelo sistema principal. O sideload é útil para depurar ou fazer alterações em dispositivos onde o sistema principal não pode ser inicializado.

Historicamente, o sideload era feito por meio do carregamento de pacotes do cartão SD do dispositivo; no caso de um dispositivo não inicializável, 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 para sideload: carregar pacotes da partição de cache e carregá-los por USB usando adb.

To invoke each sideload mechanism, your device's Device::InvokeMenuItem() method can return the following values of BuiltinAction:

  • APPLY_EXT . Sideload an update package from external storage ( /sdcard directory). Your recovery.fstab must define the /sdcard mount point. This is not usable on devices that emulate an SD card with a symlink to /data (or some similar mechanism). /data is typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in /sdcard and allows the user to select one.
  • APPLY_CACHE . Similar to loading a package from /sdcard except that the /cache directory (which is always available to recovery) is used instead. From the regular system, /cache is only writable by privileged users, and if the device isn't bootable then the /cache directory can't be written to at all (which makes this mechanism of limited utility).
  • APPLY_ADB_SIDELOAD . Allows user to send a package to the device via a USB cable and the adb development tool. When this mechanism is invoked, recovery starts up its own mini version of the adbd daemon to let adb on a connected host computer talk to it. This mini version supports only a single command: adb sideload filename . The named file is sent from the host machine to the device, which then verifies and installs it just as if it had been on local storage.

A few caveats:

  • Only USB transport is supported.
  • If your recovery runs adbd normally (usually true for userdebug and eng builds), that will be shut down while the device is in adb sideload mode and will be restarted when adb sideload has finished receiving a package. While in adb sideload mode, no adb commands other than sideload work ( logcat , reboot , push , pull , shell , etc. all fail).
  • You cannot exit adb sideload mode on the device. To abort, you can send /dev/null (or anything else that's not a valid package) as the package, and then the device will fail to verify it and stop the installation procedure. The RecoveryUI implementation's CheckKey() method will continue to be called for keypresses, so you can provide a key sequence that reboots the device and works in adb sideload mode.