Fornecedor APEX

É possível usar o formato de arquivo APEX para empacotar e instalar módulos de nível mais baixo do SO Android. Ele permite a criação e instalação independentes de componentes como serviços e bibliotecas nativas, implementações de HAL, firmware, arquivos de configuração etc.

Os APEXs do fornecedor são instalados automaticamente pelo sistema de build na partição /vendor e ativados no tempo de execução por apexd, assim como os APEXs em outras partições.

Casos de uso

Modularização de imagens de fornecedores

Os APEXs facilitam um agrupamento e uma modularização naturais de implementações de recursos em imagens de fornecedores.

Quando as imagens do fornecedor são criadas como uma combinação de APEXes do fornecedor criados de forma independente, os fabricantes de dispositivos podem escolher facilmente as implementações específicas do fornecedor que querem no dispositivo. Os fabricantes podem até criar um novo APEX de fornecedor se nenhum dos APEXes fornecidos atender às necessidades deles ou se tiverem um hardware personalizado totalmente novo.

Por exemplo, um OEM pode optar por compor o dispositivo com o APEX de implementação Wi-Fi do AOSP, o APEX de implementação Bluetooth do SoC e um APEX de implementação de telefonia personalizada do OEM.

Sem os APEXes do fornecedor, uma implementação com tantas dependências entre componentes do fornecedor exige coordenação e rastreamento cuidadosos. Ao encapsular todos os componentes (incluindo arquivos de configuração e bibliotecas extras) em APEXes com interfaces claramente definidas em qualquer ponto de comunicação entre recursos, os diferentes componentes se tornam intercambiáveis.

Iteração do desenvolvedor

Os APEXs do fornecedor ajudam os desenvolvedores a iterar mais rápido ao desenvolver módulos do fornecedor, agrupando uma implementação completa de recursos, como o HAL Wi-Fi, em um APEX do fornecedor. Em seguida, os desenvolvedores podem criar e enviar individualmente o APEX do fornecedor para testar mudanças, em vez de recriar toda a imagem do fornecedor.

Isso simplifica e acelera o ciclo de iteração para desenvolvedores que trabalham principalmente em uma área de recursos e querem iterar apenas nessa área.

O agrupamento natural de uma área de recursos em um APEX também simplifica o processo de criação, envio e teste de mudanças nessa área. Por exemplo, a reinstalação de um APEX atualiza automaticamente qualquer biblioteca ou arquivo de configuração incluído no APEX.

Agrupar uma área de recursos em um APEX também simplifica a depuração ou a reversão quando um comportamento inadequado do dispositivo é observado. Por exemplo, se a telefonia estiver funcionando mal em um novo build, os desenvolvedores poderão tentar instalar um APEX de implementação de telefonia mais antigo em um dispositivo (sem precisar atualizar um build completo) e ver se o bom comportamento é restaurado.

Exemplo de fluxo de trabalho:

# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w

# Test the device.
... testing ...

# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...

# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...

Exemplos

Noções básicas

Consulte a página principal Formato de arquivo APEX para informações gerais sobre o APEX, incluindo requisitos do dispositivo, detalhes do formato de arquivo e etapas de instalação.

Em Android.bp, definir a propriedade vendor: true transforma um módulo APEX em um APEX do fornecedor.

apex {
  ..
  vendor: true,
  ..
}

Binários e bibliotecas compartilhadas

Um APEX inclui dependências transitivas no payload, a menos que elas tenham interfaces estáveis.

As interfaces nativas estáveis para dependências do APEX do fornecedor incluem cc_library com bibliotecas stubs e LLNDK. Essas dependências são excluídas do pacote, e as dependências são registradas no manifesto do APEX. O manifesto é processado pelo linkerconfig para que as dependências nativas externas fiquem disponíveis durante a execução.

No snippet a seguir, o APEX contém o binário (my_service) e as dependências não estáveis (arquivos *.so).

apex {
  ..
  vendor: true,
  binaries: ["my_service"],
  ..
}

No snippet a seguir, o APEX contém a biblioteca compartilhada my_standalone_libe qualquer uma das dependências não estáveis dela (conforme descrito acima).

apex {
  ..
  vendor: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

Diminuir o APEX

O APEX pode ficar maior porque agrupa dependências não estáveis. Recomendamos usar a vinculação estática. Bibliotecas comuns, como libc++.so e libbase.so, podem ser vinculadas estaticamente a binários HAL. Criar uma dependência para fornecer uma interface estável pode ser outra opção. A dependência não será agrupada no APEX.

Implementações de HAL

Para definir uma implementação de HAL, forneça os binários e as bibliotecas correspondentes em um APEX do fornecedor semelhante aos exemplos a seguir:

Para encapsular totalmente a implementação da HAL, o APEX também precisa especificar todos os fragmentos VINTF e scripts de inicialização relevantes.

Fragmentos do VINTF

Os fragmentos do VINTF podem ser veiculados de um APEX do fornecedor quando estão localizados em etc/vintf do APEX.

Use a propriedade prebuilts para incorporar os fragmentos do VINTF no APEX.

apex {
  ..
  vendor: true,
  prebuilts: ["fragment.xml"],
  ..
}

prebuilt_etc {
  name: "fragment.xml",
  src: "fragment.xml",
  sub_dir: "vintf",
}

APIs de consulta

Quando os fragmentos VINTF são adicionados ao APEX, use as APIs libbinder_ndk para receber os mapeamentos de interfaces HAL e nomes APEX.

  • AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default") : true se a instância HAL estiver definida no APEX.
  • AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...): recebe o nome do APEX que define a instância da HAL.
  • AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...): use isso para abrir um HAL de passagem.

Scripts de inicialização

Os APEXs podem incluir scripts de inicialização de duas maneiras: (A) um arquivo de texto pré-criado no payload do APEX ou (B) um script de inicialização regular em /vendor/etc. É possível definir os dois para o mesmo APEX.

Script de inicialização no APEX:

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

Os scripts de inicialização em APEXes de fornecedores podem ter definições de service e diretivas on <property or event>.

Verifique se uma definição de service aponta para um binário no mesmo APEX. Por exemplo, o com.android.foo APEX pode definir um serviço chamado foo-service.

on foo-service /apex/com.android.foo/bin/foo
  ...

Tenha cuidado ao usar diretivas on. Como os scripts de inicialização em APEXes são analisados e executados depois que os APEXes são ativados, alguns eventos ou propriedades não podem ser usados. Use apex.all.ready=true para acionar ações o mais rápido possível. Os APEXs de inicialização podem usar on init, mas não on early-init.

Firmware

Exemplo:

Incorpore o firmware em um APEX do fornecedor com o tipo de módulo prebuilt_firmware, como a seguir.

prebuilt_firmware {
  name: "my.bin",
  src: "path_to_prebuilt_firmware",
  vendor: true,
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.bin"],  // installed inside APEX as /etc/firmware/my.bin
  ..
}

Os módulos prebuilt_firmware são instalados no diretório <apex name>/etc/firmware do APEX. O ueventd verifica os diretórios /apex/*/etc/firmware para encontrar módulos de firmware.

O file_contexts do APEX precisa rotular corretamente todas as entradas de payload de firmware para garantir que esses arquivos sejam acessíveis pelo ueventd no momento da execução. Normalmente, o rótulo vendor_file é suficiente. Exemplo:

(/.*)? u:object_r:vendor_file:s0

Módulos do kernel

Incorpore módulos do kernel em um APEX do fornecedor como módulos pré-criados, da seguinte maneira.

prebuilt_etc {
  name: "my.ko",
  src: "my.ko",
  vendor: true,
  sub_dir: "modules"
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.ko"],  // installed inside APEX as /etc/modules/my.ko
  ..
}

O file_contexts do APEX precisa rotular corretamente todas as entradas de payload do módulo do kernel. Exemplo:

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

Os módulos do kernel precisam ser instalados explicitamente. O exemplo a seguir de script de inicialização na partição do fornecedor mostra a instalação via insmod:

my_init.rc:

on early-boot
  insmod /apex/myapex/etc/modules/my.ko
  ..

Sobreposições de recursos de tempo de execução

Exemplo:

Incorpore substituições de recursos de tempo de execução em um APEX do fornecedor usando a propriedade rros.

runtime_resource_overlay {
    name: "my_rro",
    soc_specific: true,
}


apex {
  ..
  vendor: true,
  rros: ["my_rro"],  // installed inside APEX as /overlay/my_rro.apk
  ..
}

Outros arquivos de configuração

Os APEXs de fornecedor oferecem suporte a vários outros arquivos de configuração normalmente encontrados na partição do fornecedor como pré-compilados dentro dos APEXs de fornecedor, e mais estão sendo adicionados.

Exemplos:

APEXes de fornecedor de inicialização

Alguns serviços HAL, como keymint, precisam estar disponíveis antes da ativação dos APEXs. Essas HALs geralmente definem early_hal na definição de serviço no script de inicialização. Outro exemplo é a classe animation, que geralmente é iniciada antes do evento post-fs-data. Quando um serviço HAL inicial é empacotado em um APEX do fornecedor, faça com que o apex "vendorBootstrap": true no manifesto APEX possa ser ativado antes. Os APEXs de inicialização só podem ser ativados do local pré-criado, como /vendor/apex, e não de /data/apex.

Propriedades do sistema

Estas são as propriedades do sistema que o framework lê para oferecer suporte a APEXes do fornecedor:

  • input_device.config_file.apex=<apex name>: quando definido, os arquivos de configuração de entrada (*.idc, *.kl e *.kcm) são pesquisados no diretório /etc/usr do APEX.
  • ro.vulkan.apex=<apex name>: quando definido, o driver Vulkan é carregado do APEX. Como o driver do Vulkan é usado por HALs iniciais, crie o Bootstrap APEX do APEX e configure esse namespace do vinculador como visível.

Defina as propriedades do sistema em scripts de inicialização usando o comando setprop.

Recursos extras

Seleção do APEX na inicialização

Exemplo:

Os APEXs do fornecedor podem ser ativados opcionalmente durante a inicialização. Se você especificar um nome de arquivo usando a propriedade do sistema ro.vendor.apex.<apex name>, somente o APEX correspondente ao nome do arquivo será ativado para o <apex name> específico. O APEX com <apex name> é ignorado (não ativado) se essa propriedade do sistema for definida como none. Você pode usar esse recurso para instalar várias cópias de um APEX com o mesmo nome. Se houver várias versões do mesmo APEX, elas vão precisar compartilhar a mesma chave.

Exemplos de casos de uso:

  • Instale três versões do APEX do fornecedor do HAL Wi-Fi:as equipes de controle de qualidade podem executar testes manuais ou automatizados usando uma versão, reiniciar em outra versão e executar os testes novamente, comparando os resultados finais.
  • Instale duas versões do APEX do fornecedor HAL da câmera, atual e experimental: os usuários internos podem usar a versão experimental sem baixar e instalar um arquivo adicional, facilitando a troca de volta.

Durante a inicialização, o apexd procura sysprops seguindo um formato específico para ativar a versão correta do APEX.

Os formatos esperados para a chave da propriedade são:

  • Bootconfig
    • Usado para definir o valor padrão em BoardConfig.mk.
    • androidboot.vendor.apex.<apex name>
  • Sysprop persistente
    • Usado para mudar o valor padrão, definido em um dispositivo já inicializado.
    • Substitui o valor de bootconfig, se presente.
    • persist.vendor.apex.<apex name>

O valor da propriedade precisa ser o nome do arquivo do APEX que será ativado ou none para desativar o APEX.

// Default version.
apex {
  name: "com.oem.camera.hal.my_apex_default",
  vendor: true,
  ..
}

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  vendor: true,
  ..
}

A versão padrão também precisa ser configurada usando bootconfig em BoardConfig.mk:

# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
    androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default

Depois que o dispositivo for inicializado, mude a versão ativada definindo a sysprop persistente:

$ adb root;
$ adb shell setprop \
    persist.vendor.apex.com.oem.camera.hal \
    com.oem.camera.hal.my_apex_experimental;
$ adb reboot;

Se o dispositivo for compatível com a atualização do bootconfig após a atualização (por exemplo, usando comandos fastboot oem), mudar a propriedade do bootconfig para o APEX multi-instalado também mudará a versão ativada na inicialização.

Para dispositivos de referência virtuais baseados no Cuttlefish, use o comando --extra_bootconfig_args para definir a propriedade bootconfig diretamente durante a inicialização. Exemplo:

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";