Fornecedor APEX

É possível usar o formato de arquivo APEX para empacotar e instalar módulos de nível inferior do SO Android. Ele permite a criação e instalação independentes de componentes como serviços e bibliotecas nativos, 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 pelo apexd, assim como os APEXs em outras partições.

Casos de uso

Modularização de imagens do fornecedor

Os APEXs facilitam o agrupamento e a modularização naturais das implementações de recursos em imagens do fornecedor.

Quando as imagens do fornecedor são criadas como uma combinação de APEXs 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é mesmo criar um novo APEX do fornecedor se nenhum dos APEXs fornecidos atender às necessidades deles ou se eles tiverem um novo hardware personalizado.

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

Sem os APEXs do fornecedor, uma implementação com tantas dependências entre os componentes do fornecedor exige coordenação e acompanhamento cuidadosos. Ao agrupar todos os componentes (incluindo arquivos de configuração e bibliotecas extras) em APEXs 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 rapidamente ao desenvolver módulos do fornecedor, agrupando uma implementação de recurso inteira, como o HAL de Wi-Fi, em um APEX do fornecedor. Os desenvolvedores podem criar e enviar individualmente o APEX do fornecedor para testar as mudanças, em vez de recriar toda a imagem do fornecedor.

Isso simplifica e acelera o ciclo de iteração do desenvolvedor para aqueles 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 para essa área. Por exemplo, a reinstalação de um APEX atualiza automaticamente qualquer biblioteca ou arquivo de configuração agrupado que o APEX inclua.

O agrupamento de 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 uma nova versão, os desenvolvedores poderão tentar instalar um APEX de implementação de telefonia mais antigo em um dispositivo (sem precisar fazer o flash de uma versão completa) e verificar 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 genéricas sobre 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 tenham interfaces estáveis.

As interfaces nativas estáveis para dependências de APEX do fornecedor incluem cc_library com stubs e bibliotecas 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 estejam disponíveis no tempo de execução.

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

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

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

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

Diminuir o APEX

O APEX pode aumentar de tamanho porque agrupa dependências não estáveis. Recomendamos o uso de vinculação estática. Bibliotecas comuns, como libc++.so e libbase.so, podem ser vinculadas estaticamente a binários HAL. Tornar 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 bibliotecas correspondentes em um APEX do fornecedor semelhante aos exemplos a seguir:

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

Fragmentos VINTF

Os fragmentos 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 VINTF no APEX.

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

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

Consultar APIs

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 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 normal 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 APEXs do fornecedor podem ter service definições e on <property or event> diretivas.

Verifique se uma definição de service aponta para um binário no mesmo APEX. Por exemplo, o APEX com.android.foo 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 APEXs são analisados e executados depois que os APEXs são ativados, alguns eventos ou propriedades não podem ser usados. Use apex.all.ready=true para acionar ações o mais cedo 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, conforme mostrado abaixo.

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.

ueventd

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 tempo de execução. Normalmente, o rótulo vendor_file é suficiente. Por 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, conforme mostrado abaixo.

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. Por exemplo:

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

Os módulos do kernel precisam ser instalados explicitamente. O exemplo de script de inicialização a seguir 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 sobreposiçõ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 do fornecedor oferecem suporte a vários outros arquivos de configuração normalmente encontrados na partição do fornecedor como pré-criados dentro dos APEXs do fornecedor, e mais estão sendo adicionados.

Exemplos:

APEXs de inicialização do fornecedor

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

Propriedades do sistema

Estas são as propriedades do sistema que o framework lê para oferecer suporte a APEXs 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 Vulkan é usado por HALs iniciais, crie o APEX de inicialização e configure esse namespace do vinculador visível.

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

Recursos extras

Seleção de 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>, apenas o APEX correspondente ao nome do arquivo será ativado para o <apex name>. O APEX com <apex name> será ignorado (não ativado) se essa propriedade do sistema estiver definida como none. É possível 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 precisarão compartilhar a mesma chave.

Exemplos de casos de uso:

  • Instalar três versões do APEX do fornecedor HAL de Wi-Fi:as equipes de QA podem executar testes manuais ou automatizados usando uma versão, reiniciar em outra versão e executar os testes novamente e, em seguida, comparar os resultados finais.
  • Instalar duas versões do APEX do fornecedor HAL de câmera, atual e experimental: os usuários de teste podem usar a versão experimental sem fazer o download e instalar um arquivo adicional, para que possam alternar facilmente.

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 de 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 do 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 o 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 o 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 oferecer suporte à atualização do bootconfig após o flash (por exemplo, usando comandos fastboot oem), a mudança da propriedade do bootconfig para o APEX multi-instalado também mudará a versão ativada na inicialização.

Para dispositivos de referência virtual baseados no Cuttlefish, é possível usar o comando --extra_bootconfig_args para definir a propriedade do bootconfig diretamente durante a inicialização. Por exemplo:

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