Código específico do dispositivo

O sistema de recuperação inclui vários ganchos para inserir código específico do dispositivo, de modo que o OTA as atualizações também podem atualizar partes do dispositivo que não sejam o sistema Android (por exemplo, a banda de base ou processador de rádio).

As seções e os exemplos a seguir personalizam o dispositivo tardis produzido pelo fornecedor yoyodyne.

Mapa de partições

A partir do Android 2.3, a plataforma oferece suporte a dispositivos flash eMMc e ao sistema de arquivos ext4 que é executado nesses dispositivos. Ele também é compatível com dispositivos flash de dispositivos de tecnologia de memória (MTD, na sigla em inglês) e o sistema de versões mais antigas.

O arquivo do mapa de partições é especificado por TARGET_RECOVERY_FSTAB; que o arquivo seja usado e das ferramentas de criação de pacotes. Você pode especificar o nome do arquivo de mapa em TARGET_RECOVERY_FSTAB em BoardConfig.mk.

Um exemplo de arquivo de mapa de partições pode ter a seguinte 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 precisa ser definido (os dispositivos também podem adicionar partições extras). Há cinco suportes tipos de sistema de arquivos:

yaffs2
Um sistema de arquivos yaffs2 sobre um dispositivo flash MTD. "dispositivo" 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. O MTD não é montado, mas o ponto de montagem é usado como chave para localizar a partição. "dispositivo" precisa ser o nome da partição MTD em /proc/mtd.
ext4
Um sistema de arquivos ext4 sobre um dispositivo flash eMMc. "dispositivo" precisa ser o caminho do dispositivo de transferência por blocos.
Emmc
Um dispositivo de bloco eMMc bruto, usado para partições inicializáveis, como inicialização e recuperação. Semelhante a do tipo mtd, eMMc nunca é realmente montado, mas a string do ponto de montagem é usada para localizar o dispositivo na mesa.
vfat
Um sistema de arquivos FAT sobre um dispositivo de transferência por blocos, normalmente para armazenamento externo, como um cartão SD. A device é o dispositivo de bloco, device2 é um segundo dispositivo em bloco que o sistema tenta montar caso a montagem do dispositivo principal falha (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 comece com uma barra e não tenha outras barras). Essa restrição se aplica apenas à montagem sistemas de arquivos em recuperação, o sistema principal é livre para montá-los em qualquer lugar. Os diretórios /boot, /recovery e /misc precisam ser tipos brutos (mtd ou emmc), enquanto os diretórios /system, /data, /cache e /sdcard (se disponível) devem ser tipos de sistema de arquivos (yaffs2, ext4 ou vfat).

A partir do Android 3.0, o arquivo recovery.fstab recebe mais um campo opcional: opções. Atualmente, a única opção definida é length , que permite explicitamente especificar o comprimento da partição. Esse tamanho é 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 o partição do sistema durante a instalação de um pacote OTA completo). Se o valor de comprimento for negativo, o tamanho a ser formatado é usado adicionando o valor do comprimento ao tamanho real da partição. Para instância, definindo “length=-16384” significa que os últimos 16 K dessa partição não serão substituída quando a partição for reformatada. Ele dá suporte a recursos como criptografia na partição userdata (onde os metadados de criptografia são armazenados no final da partição que não deve ser substituída).

Observação: os campos device2 e options são opcionais, criando ambiguidade na análise. Se a entrada no quarto campo da linha começar com "/" ela será considerada uma entrada device2 ; se a entrada não começar com "/" ele será considerado um campo options.

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 de acordo com as especificações em formato bootAnimation.

Para Dispositivos Android Things, faça upload do arquivo compactado no console do Android Things para incluir as imagens no o produto selecionado.

Observação:essas imagens precisam atender às diretrizes da promoção de marca do Android. Para as diretrizes de marca, consulte a seção do Android Marketing do parceiro Hub.

interface 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 o sistema recursos ocultos para cada dispositivo.

Seu objetivo é criar uma pequena biblioteca estática com alguns objetos C++ para fornecer a funcionalidades específicas do dispositivo. O arquivo bootable/recovery/default_device.cpp é usado por padrão e é um bom ponto de partida a ser copiado ao gravar uma versão deste arquivo para seu dispositivo.

Observação:a mensagem No Command talvez apareça aqui. Para alternar texto, mantenha o botão liga/desliga pressionado enquanto pressiona o botão de aumentar o volume. Se seus dispositivos não tiverem os dois botões. Mantenha qualquer botão pressionado 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 no menu de recuperação. Os cabeçalhos descrevem como operar o menu (ou seja, controles para alterar/selecionar o item destacado).

static const char* HEADERS[] = { "Volume up/down to move highlight;",
                                 "power button to select.",
                                 "",
                                 NULL };

static const char* ITEMS[] =  {"reboot system now",
                               "apply update from ADB",
                               "wipe data/factory reset",
                               "wipe cache partition",
                               NULL };

Observação:as linhas longas são truncadas (não quebradas). Portanto, mantenha a largura das da tela do dispositivo em mente.

Personalizar chave de verificação

Em seguida, defina a implementação da RecoveryUI do dispositivo. Neste exemplo, pressupomos O dispositivo tardis tem uma tela, que pode ser herdada do sistema ScreenRecoveryUIimplementation (consulte as instruções para dispositivos sem tela. A única função para personalizar da ScreenRecoveryUI é CheckKey(), que faz o processamento assíncrono de chaves:

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

Constantes KEY

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

  • ATIVAR. Ativar ou desativar a exibição do menu e/ou o registro de texto
  • REINICIAR. Reinicie o dispositivo imediatamente
  • IGNORAR. Ignorar esta pressionamento de tecla
  • NA FILA. Enfileirar esta tecla pressionada para consumo síncrono (ou seja, pelo sistema de recuperação sistema de menu se a tela estiver ativada)

CheckKey() é chamado sempre que um evento de tecla para baixo é seguido por um evento de pressionamento de tecla para a mesma chave. (A sequência de eventos A-down B-down B-up A-up resulta apenas em CheckKey(B) sendo chamado. CheckKey() pode ligar IsKeyPressed(), para descobrir se outras teclas estão sendo pressionadas. (No exemplo acima, sequência de eventos de tecla, se CheckKey(B) chamou IsKeyPressed(A), retornaria "true".

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 pela Manter o botão liga/desliga pressionado e pressionar 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;
    }
};

Interface de recuperação de tela

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; quadros por segundo (QPS) das animações.

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

Para definir a variável animation_fps, modifique o ScreenRecoveryUI::Init() na subclasse. Defina o valor e chame o método 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, você não precisa fornecer uma função Init(). Para detalhes sobre imagens, consulte Imagens da interface de recuperação.

Classe do dispositivo

Depois de implementar a RecoveryUI, defina a classe do dispositivo, que é uma subclasse da classe do dispositivo integrada). Ele deve criar uma única instância da sua classe de interface e retornar essa da função GetUI():

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

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

    RecoveryUI* GetUI() { return ui; }

IniciarRecuperação

O método StartRecovery() é chamado no início da recuperação, depois que a interface é inicializado e após os argumentos terem sido analisados, mas antes que qualquer ação tenha sido tomadas. A implementação padrão não faz nada. Por isso, não é necessário fornecer isso no subclasse se você não tem 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. Neste implementação, ela retorna as matrizes estáticas definidas 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 usa um pressionamento de tecla e o a visibilidade do menu e decide a ação a ser tomada:

   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 colocado na fila pelo método CheckKey() do objeto da interface) e o estado atual do menu/registro de texto visibilidade. O valor de retorno é um número inteiro. Se o valor for 0 ou maior, ele será usado como posição de um item de menu, que é invocada imediatamente (consulte a InvokeMenuItem() abaixo). Caso contrário, pode ser um dos seguintes: constantes predefinidas:

  • kHighlightUp: Mover o destaque do menu para o item anterior
  • kHighlightDown (em inglês). Mover o destaque do menu para o próximo item
  • kChamadasItem. Invocar o item destacado no momento
  • kNoAction. Não fazer nada com esta tecla pressionada

Como implícito pelo argumento visible, HandleMenuKey() é chamado mesmo se o menu for visível. Ao contrário de CheckKey(), ele não é chamado durante a recuperação. algo como a exclusão permanente dos dados ou a instalação de um pacote; esse método só é chamado quando a recuperação está ociosa. e aguardando as informações.

Mecanismos de trackball

Se o dispositivo tiver um mecanismo de entrada semelhante a trackball (gera eventos de entrada com o tipo EV_REL) e o código REL_Y), a recuperação sintetiza as teclas KEY_UP e KEY_DOWN sempre que a o dispositivo de entrada semelhante ao trackball informa o movimento no eixo Y. Tudo que você precisa fazer é mapear KEY_UP e KEY_DOWN em ações do menu. Esse mapeamento não acontece para CheckKey(), portanto, não é possível usar os movimentos do trackball como acionadores para reinicialização ou alternando a tela.

Teclas modificadoras

Para conferir se há teclas pressionadas como modificadores, chame o método IsKeyPressed() . do seu próprio objeto de IU. Por exemplo, em alguns dispositivos, pressionar Alt-W na recuperação inicia uma a exclusão permanente de dados, independentemente de o menu estar visível ou não. Você poderia implementar da seguinte forma:

   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 o valor especial valores que manipulam o menu (mover o destaque, invocar o item destacado), já que o usuário não pode o destaque. No entanto, é possível retornar os valores, se você quiser.

InvocarMenuItem

Em seguida, forneça um método InvokeMenuItem() que mapeie posições de números inteiros na matriz de itens retornados por GetMenuItems() para ações. Para a matriz de itens na 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 EmbedinAction para instruir o sistema a realizar essa ação. ação (ou o membro NO_ACTION se desejar que o sistema não faça nada). Este é o lugar para fornecem funcionalidade de recuperação adicional, além do que está no sistema: adicionar um item ao seu menu, executá-lo aqui quando o item de menu for invocado e retornar NO_ACTION para que o sistema não faz mais nada.

builtinAction contém os seguintes valores:

  • NO_ACTION. Não fazer nada.
  • REINICIAR. Saia da recuperação e reinicie o dispositivo normalmente.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Instalar pacotes de atualização de vários lugares. Para mais detalhes, consulte Sideload.
  • LIMPAR_CACHE. Reformate apenas a partição de cache. Nenhuma confirmação é necessária, porque isso é relativamente inofensivos.
  • WIPE_DATA. Reformatar as partições de cache e userdata, também conhecidas como dados de fábrica redefinido. O usuário precisa confirmar a ação antes de continuar.

O último método, WipeData(), é opcional e é chamado sempre que uma exclusão permanente de dados acontece. for iniciada (na recuperação pelo menu ou quando o usuário optar por realizar uma redefinir para a configuração original pelo sistema principal). Esse método é chamado antes dos dados do usuário e do cache as partições são apagadas. Se o dispositivo armazenar dados do usuário em qualquer lugar além desses dois partições diferentes, apague-as aqui. Você deve retornar 0 para indicar sucesso e outro valor de falha, embora atualmente o valor de retorno seja ignorado. Os dados e o cache do usuário as partições são excluídas, mesmo que você retorne sucesso ou falha.

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

Fabricante do dispositivo

Por fim, inclua um código boilerplate no final do arquivo recovery_ui.cpp para 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-o e vincule-o à recuperação no seu dispositivo. Em Android.mk, crie uma biblioteca estática que contenha apenas esse 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 deste dispositivo, especifique sua biblioteca estática como o valor de TARGET_RECOVERY_UI_LIB.

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

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

Imagens da interface de recuperação

A interface do usuário de recuperação consiste em imagens. O ideal é que os usuários nunca interajam com a interface: Durante uma atualização normal, o smartphone inicializa na recuperação, preenche a barra de progresso da instalação, e inicializa de volta no novo sistema sem interação do usuário. Caso haja um sistema problema de atualização, a única ação do usuário que pode ser realizada é ligar para o atendimento ao cliente.

Uma interface apenas de imagem elimina a necessidade de localização. No entanto, a partir do Android 5.0, atualização pode exibir uma sequência de texto (por exemplo, "Instalando atualização do sistema...") junto com a imagem. Para mais detalhes, consulte Texto de recuperação localizado.

Android 5.0 e mais recente

A interface de recuperação do Android 5.0 e versões posteriores usa duas imagens principais: a imagem de erro e a animação de instalação.

imagem mostrada durante erro de 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 frames diferentes do animação entrelaçada por linha (por isso, a Figura 2 aparece apertada). Por exemplo, para um Animação com sete frames de 200 x 200, crie uma única imagem de 200 x 1400 com o primeiro frame das linhas 0, 7 14, 21, ...; o segundo frame é: linhas 1, 8, 15, 22, ...; A imagem combinada inclui uma bloco de texto que indica o número de frames de animação e o número de frames por segundo (QPS). A ferramenta bootable/recovery/interlace-frames.py usa um conjunto de frames de entrada e as 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 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 frames no animação para 0 (o ícone de erro não é animado, é sempre uma imagem estática).

Android 4.x e anterior

A interface de recuperação do Android 4.x e versões anteriores usam a imagem de erro (mostrada acima) e as installing e várias imagens de sobreposição:

imagem mostrada durante a instalação 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 o icon_installing.png e desenhando um dos frames de sobreposição em cima dela no deslocamento adequado. Aqui, um vermelho é sobreposta para destacar onde a sobreposição é colocada em cima da imagem de base:

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

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

imagem composta de
instalação mais sétima sobreposição

Figura 7. Instalando o frame 7 da animação (icon_installing.png + icon_installing_overlay07.png)

Os frames seguintes são exibidos desenhando apenas a próxima imagem de sobreposição sobre a já está lá; a imagem base não será 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, modifique o método Init() na para alterar esses valores nas imagens personalizadas (para obter detalhes, consulte ScreenRecoveryUI). O script O bootable/recovery/make-overlay.py pode ajudar na conversão de um conjunto de frames de imagem "imagem de base + imagens de sobreposição" necessário para a recuperação, incluindo o cálculo da os deslocamentos necessários.

As imagens padrão ficam em bootable/recovery/res/images. Para usar uma imagem estática durante a instalação, só é necessário fornecer a imagem icon_installing.png e definir o número de frames 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 string de texto (por exemplo, "Instalando atualização do sistema...") junto com o imagem. Quando o sistema principal inicializa na recuperação, ele passa a localidade atual do usuário como um opção de linha de comando para 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 exibir as seguintes mensagens:

  • Instalando atualização do sistema...
  • Erro!
  • Apagando... (ao fazer uma exclusão permanente de dados/redefinição para a configuração original)
  • Nenhum comando (quando um usuário inicializa manualmente na recuperação)

O app Android no bootable/recovery/tools/recovery_l10n/ renderiza localizações de uma mensagem e cria a imagem composta. Para saber como usar esse app, consulte a 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 exibidos. Não torne as mensagens de texto essenciais para o processo de recuperação.

Observação: a interface oculta que exibe mensagens de registro e permite que o usuário a seleção de ações do menu está disponível apenas em inglês.

Barras de progresso

As barras de progresso podem aparecer abaixo da imagem (ou animação) principal. A barra de progresso é feita por combinar duas imagens de entrada, que precisam 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 de preenchimento é exibida ao lado da extremidade direita do vazia para criar a barra de progresso. A posição do limite entre os dois imagens é alterado para indicar o progresso. Por exemplo, com os pares de imagens de entrada acima, exibir:

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 para dispositivos dessas imagens colocando-as em (neste exemplo) device/yoyodyne/tardis/recovery/res/images , Os nomes dos arquivos devem corresponder aos listados acima. quando um arquivo é encontrado nesse diretório, sistema de build a utiliza em vez da imagem padrão correspondente. Somente PNGs em RGB ou O formato RGBA com profundidade de cor de 8 bits é aceito.

Observação:no Android 5.x, se a localidade for conhecida pela recuperação e for um idioma da direita para a esquerda (RTL) (árabe, hebraico etc.), a barra de progresso é preenchida da direita para sobrar mais

Dispositivos sem telas

Nem todos os dispositivos Android têm telas. Se o dispositivo for um dispositivo headless ou tiver um do usuário, talvez seja necessário personalizar mais a interface de recuperação. Em vez de criar uma subclasse de ScreenRecoveryUI, crie uma subclasse direta da classe pai RecoveryUI.

A RecoveryUI tem métodos para lidar com operações de interface de nível inferior, como "alternar a tela", "atualizar a barra de progresso", "mostrar o cardápio", "mudar a seleção do menu", etc. Você pode substituir para fornecer uma interface adequada para seu dispositivo. Talvez seu dispositivo tenha LEDs você pode usar diferentes cores ou padrões de piscar para indicar o estado, ou talvez você possa tocar áudio. (Talvez você não queira oferecer suporte a um menu ou modo de "exibição de texto". Você pode impedir o acesso com CheckKey() e Implementações HandleMenuKey() que nunca ativam a tela ou selecionam um menu do item de linha. Nesse caso, muitos dos métodos RecoveryUI que você precisa fornecer podem ficar vazios bate-papos?

Consulte bootable/recovery/ui.h para a declaração de RecoveryUI para ver quais métodos que você precisa oferecer. A RecoveryUI é abstrata. Alguns métodos são puramente virtuais e precisam ser fornecidos mas contém o código para processar as entradas de chave. É possível substituir essa também, se o dispositivo não tiver chaves ou se você quiser processá-las de forma diferente.

Updater

Você pode usar o código específico do dispositivo na instalação do pacote de atualização fornecendo seu próprias funções de extensão que podem ser chamadas de dentro do script do atualizador. Aqui está um exemplo 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 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 é chamada, ou seja, a lógica determina quais deles são avaliados e quantas vezes. Assim, você pode usar extensões funções 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 estiver mantendo e retornar NULL imediatamente (esse propaga é cancelado na pilha do edify). Caso contrário, você assume a propriedade do valor retornado e são responsáveis por chamar FreeValue() nela.

Suponha que a função precise de dois argumentos: uma key com valor de string e um com valor de blob. image. Você poderia 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 se há NULL e liberar argumentos avaliados anteriormente pode ser tedioso por várias . A função ReadValueArgs() pode facilitar isso. Em vez do código acima, você poderia ter escrito isto:

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

O ReadValueArgs() não faz a verificação de tipos, então você precisa fazer isso aqui. é mais é conveniente fazer isso com uma instrução if ao custo de produzir uma resposta um pouco menos uma mensagem de erro específica em caso de falha. Mas o ReadValueArgs() processa a avaliação de cada argumento e liberando todos os argumentos avaliados anteriormente (bem como definir uma mensagem de erro) se alguma das avaliações falhar. Você pode usar um Função de conveniência ReadValueVarArgs() para avaliar um número variável de (ele retorna uma matriz de Value*).

Depois de avaliar os argumentos, execute 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 deste objeto será transferida para o autor da chamada. O autor da chamada assume a propriedade de todos os dados para os quais ele aponta Value*: especificamente o datamember.

Nesse caso, você quer retornar um valor verdadeiro ou falso para indicar o sucesso. Lembre-se do convenção de que a string vazia é false e todas as outras strings são true. Você precisa inserir um objeto de valor com uma cópia maliciosa da string constante a ser retornada, já que o autor da chamada usará free() em ambos. Não se esqueça de chamar FreeValue() no objetos obtidos ao avaliar seus argumentos.

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

A função de conveniência StringValue() encapsula uma string em um novo objeto Value. Use para escrever o código acima de forma mais sucinta:

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

Para conectar funções ao intérprete do edify, forneça a função Register_foo, em que foo é o nome da biblioteca estática que contém para esse código. Chame RegisterFunction() para registrar cada função de extensão. De convenção, nomeie funções específicas do dispositivo como device.whatever para evitar entra em conflito 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. (Isto é o mesmo makefile usado para personalizar a interface de recuperação na seção anterior; o dispositivo pode ter 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 corresponder ao nome da Register_libname contida nele.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Por fim, configure a compilação de recuperação para extrair sua biblioteca. Adicionar sua biblioteca a TARGET_RECOVERY_UPDATER_LIBS (que pode conter várias bibliotecas; todas são registradas). Se o código depender de outras bibliotecas estáticas que não sejam extensões do Education (por exemplo, não tiverem uma função Register_libname), liste-os em TARGET_RECOVERY_UPDATER_EXTRA_LIBS para vinculá-los ao atualizador sem chamar (inexistente). Por exemplo, se o código específico do dispositivo quisesse 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 atualizador no pacote OTA agora podem chamar sua função como qualquer outro. Para reprogramar seu dispositivo tardis, o script de atualização poderá 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 último componente é fazer com que as ferramentas de geração de pacotes OTA conheçam seu dados específicos do dispositivo e emitem scripts de atualizador que incluem chamadas para as funções de extensão.

Primeiro, faça com que o sistema de compilação saiba sobre um blob de dados específico do dispositivo. Supondo que seus dados arquivo estiver em device/yoyodyne/tardis/tardis.dat, declare o seguinte no seu AndroidBoard.mk do dispositivo:

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

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

Você também pode colocá-los em um Android.mk, mas precisa ser protegido por um dispositivo uma vez que todos os arquivos Android.mk na árvore são carregados, independentemente do dispositivo construído. Se sua árvore incluir vários dispositivos, convém adicionar apenas o arquivo tardis.dat ao a criação do 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; elas podem não ter nada a ver com rádio do dispositivo (se houver). Eles são simplesmente blobs de dados opacos que o sistema de build copia para o o target-files .zip usado pelas ferramentas de geração OTA. Quando você cria um build, o tardis.dat é armazenado no target-files.zip como RADIO/tardis.dat. Você pode ligar add-radio-file várias vezes para adicionar quantos arquivos você quiser.

Módulo do Python

Para estender as ferramentas de lançamento, escreva um módulo Python (deve ser nomeado releasetools.py) das ferramentas pode chamar se estiver presente. 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 da geração de um pacote OTA incremental. Para isso Por exemplo, suponha que você precise reprogramar o tardis somente quando o arquivo tardis.dat tiver mudado entre dois builds.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Funções do módulo

Você pode fornecer as seguintes funções no módulo (implemente apenas as que forem necessárias).

FullOTA_Assertions()
Chamado perto do início da geração de uma OTA completa. Esse é um bom lugar para emitir declarações sobre o estado atual do dispositivo. Não emita comandos de script que fazem alterações no dispositivo.
FullOTA_InstallBegin()
Chamado depois que todas as declarações sobre o estado do dispositivo tiverem passado, mas antes de qualquer mudança foram feitas. É possível emitir comandos para atualizações específicas do dispositivo que precisam ser executadas antes qualquer outra coisa no dispositivo foi alterada.
FullOTA_InstallEnd()
Chamado no final da geração do script, depois dos comandos do script para atualizar a inicialização e partições do sistema foram emitidas. Também é possível emitir comandos adicionais para e atualizações específicas para cada dispositivo.
IncrementalOTA_Assertions()
Semelhante a FullOTA_Assertions(), mas chamado ao gerar um valor incremental update.
IncrementalOTA_VerifyBegin()
Chamado após todas as declarações sobre o estado do dispositivo terem passado, mas antes que qualquer mudança tenha sido ter sido feita. Você pode emitir comandos para atualizações específicas do dispositivo que precisam ser executados antes de qualquer ou outro no dispositivo foi alterado.
IncrementalOTA_VerifyEnd()
Chamado no final da fase de verificação, quando o script termina de confirmar a os arquivos que ele vai tocar têm o conteúdo inicial esperado. Neste ponto, nada na dispositivo foi alterado. Também é possível emitir códigos para outros comandos verificações.
IncrementalOTA_InstallBegin()
Chamado depois que os arquivos a serem corrigidos têm o erro esperado antes do estado, mas antes de qualquer mudança ser 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_InstallEnd()
Semelhante ao pacote OTA completo, ele é chamado no final do script. geração de registros, depois que os comandos de script para atualizar as partições de inicialização e sistema foram emitidos. Também é possível emitir comandos adicionais para atualizações específicas do dispositivo.

Observação:se o dispositivo ficar sem energia, a instalação OTA poderá ser reiniciada no desde o início. Prepare-se para lidar com dispositivos em que esses comandos já foram executados. total ou parcialmente.

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

Transmita funções para um único objeto de informação que contenha vários itens úteis:

  • info.input_zip. (Somente OTAs completas) O objeto zipfile.ZipFile do de entrada target-files .zip.
  • info.source_zip. (Somente OTAs incrementais) O objeto zipfile.ZipFile para o arquivo .zip de origem de destino (o build já estava no dispositivo quando o pacote incremental está sendo instalado).
  • info.target_zip. (Somente OTAs incrementais) O objeto zipfile.ZipFile para target-files .zip (a compilação que o pacote incremental coloca no dispositivo).
  • info.output_zip. O pacote está sendo criado. um objeto zipfile.ZipFile foi aberto para escrever. Use common.ZipWriteStr(info.output_zip, filename, data) para adicionar um no pacote.
  • info.script. Objeto de script ao qual você pode anexar comandos. Ligação info.script.AppendExtra(script_text) para gerar texto no script. Verifique se o texto de saída termina com ponto e vírgula para que não execute comandos emitidos depois.

Para detalhes sobre o objeto de informações, consulte Documentação da Python Software Foundation para arquivos ZIP (em inglês).

Especificar o local do módulo

Especifique a localização 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 está definida, o padrã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 nos arquivos de destino Arquivo .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 arquivo .zip de target-files, se presente, é preferível em relação à da árvore de origem do Android. Também é possível definir especifique o caminho para as extensões específicas do dispositivo com o -s (ou --device_specific), que tem a prioridade máxima. Isso permite que você corrigir os erros, fazer alterações nas extensões Releasetools e aplicar essas alterações às target-files.

Agora, quando você executar ota_from_target_files, ele vai coletar automaticamente módulo específico do dispositivo do arquivo .zip target_files e o utiliza ao gerar dados OTA pacotes:

./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 uma lista completa de opções, consulte ota_from_target_files comentários 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 fazendo o download over the air pelo sistema principal. O sideload é útil para depurar ou criar mudanças nos dispositivos em que não é possível inicializar o sistema principal.

Historicamente, o sideload é feito com o carregamento de pacotes fora do cartão SD do dispositivo. no no caso de um dispositivo que não seja de inicialização, o pacote pode ser colocado no cartão SD usando algum outro computador e o cartão SD inserido no dispositivo. Para acomodar dispositivos Android sem armazenamento externo removível, a recuperação suporta dois outros mecanismos de sideload: carregar pacotes da partição de cache e carregá-los via USB usando o adb.

Para invocar cada mecanismo de sideload, o método Device::InvokeMenuItem() do dispositivo pode retornar os seguintes valores de EmbedinAction:

  • APPLY_EXT. Transferir um pacote de atualização do armazenamento externo por sideload ( /sdcard ). O "recovery.fstab" precisa definir o ponto de montagem /sdcard . Isso é não pode ser usado em dispositivos que emulam um cartão SD com um link simbólico para /data (ou alguns mecanismo similar). Normalmente, o /data não está disponível para recuperação porque está podem ser criptografados. A interface de recuperação mostra um menu de arquivos ZIP em /sdcard e permite que o usuário selecione um.
  • APLICAR_CACHE. É semelhante ao carregamento de um pacote do /sdcard, mas o O diretório /cache (que está sempre disponível para recuperação) é usado. como alternativa. No sistema normal, /cache só é gravável por usuários privilegiados, Se o dispositivo não for inicializável, o diretório /cache não poderá ser gravado (o que torna esse mecanismo de utilidade limitada).
  • APPLY_ADB_SIDELOAD. Permite que o usuário envie um pacote para o dispositivo por meio de um cabo USB e a ferramenta de desenvolvimento adb. Quando esse mecanismo é invocado, a recuperação inicia seu próprio mini do daemon do adbd para permitir que o adb em um computador host conectado se comunique com ele. Este mini compatível com apenas um comando: adb sideload filename. O arquivo nomeado é enviado da máquina host para o dispositivo, que verifica e o 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), isso será será desligado enquanto o dispositivo estiver no modo de sideload adb e será reiniciado quando o adb sideload terminou de receber um pacote. No modo de sideload do adb, nenhum comando adb outro do que sideload funcionam ( logcat, reboot, push, pull, shell etc. falham).
  • Não é possível sair do modo de sideload do adb no dispositivo. Para cancelar, envie: /dev/null (ou qualquer outra coisa que não seja um pacote válido) como o pacote, e o dispositivo não o verificará e interromperá o procedimento de instalação. RecoveryUI o método CheckKey() da implementação vai continuar sendo chamado para pressionamentos de tecla, para que você possa fornecer uma sequência de teclas que reinicia o dispositivo e funciona no modo de sideload do adb.