O Android 11 introduz a capacidade de usar AIDL para HALs no Android, tornando possível implementar partes do Android sem HIDL. Faça a transição das HALs para usar a AIDL exclusivamente sempre que possível (quando as HALs upstream usam HIDL, o HIDL precisa ser usado).
Os HALs que usam a AIDL para se comunicar entre componentes do framework, como os em
system.img, e componentes de hardware, como os em vendor.img, precisam usar
AIDL estável. No entanto, para se comunicar dentro de uma partição, por exemplo, de um
HAL para outro, não há restrição quanto ao mecanismo de IPC a ser usado.
Motivação
O AIDL existe há mais tempo que o HIDL e é usado em muitos outros lugares, como entre componentes do framework Android ou em apps. Agora que a AIDL tem suporte à estabilidade, é possível implementar uma pilha inteira com um único tempo de execução de IPC. O AIDL também tem um sistema de controle de versões melhor do que o HIDL. Confira algumas vantagens da AIDL:
- Usar uma única linguagem de IPC significa ter apenas uma coisa para aprender, depurar, otimizar e proteger.
- A AIDL oferece suporte ao controle de versões in loco para os proprietários de uma interface:
- Os proprietários podem adicionar métodos ao final das interfaces ou campos a parcelables. Isso significa que é mais fácil versionar o código ao longo dos anos, e o custo anual também é menor. Os tipos podem ser alterados no lugar, e não há necessidade de bibliotecas extras para cada versão de interface.
- As interfaces de extensão podem ser anexadas no tempo de execução em vez de no sistema de tipos. Assim, não é necessário rebasear extensões downstream em versões mais recentes de interfaces.
- Uma interface AIDL pode ser usada diretamente quando o proprietário decide estabilizá-la. Antes, era necessário criar uma cópia inteira da interface em HIDL.
Criar com base no ambiente de execução da AIDL
A AIDL tem três back-ends diferentes: Java, NDK e CPP. Para usar o AIDL estável,
sempre use a cópia do sistema de libbinder em system/lib*/libbinder.so e
fale em /dev/binder. Para o código na imagem vendor, isso significa que libbinder (do VNDK) não pode ser usado: essa biblioteca tem uma API C++ instável e internos instáveis. Em vez disso, o código nativo do fornecedor precisa usar o back-end do NDK
da AIDL, vincular a libbinder_ndk (que é compatível com libbinder.so
do sistema) e vincular às bibliotecas do NDK criadas por entradas
aidl_interface. Para saber os nomes exatos dos módulos, consulte Regras de nomenclatura de módulos.
Escrever uma interface HAL AIDL
Para que uma interface AIDL seja usada entre o sistema e o fornecedor, ela precisa de duas mudanças:
- Cada definição de tipo precisa ser anotada com
@VintfStability. - A declaração
aidl_interfaceprecisa incluirstability: "vintf",.
Apenas o proprietário de uma interface pode fazer essas mudanças.
Quando você faz essas mudanças, a interface precisa estar no manifesto VINTF para funcionar. Teste isso (e requisitos relacionados, como verificar se as interfaces lançadas estão congeladas) usando o teste do pacote de testes de fornecedor (VTS) vts_treble_vintf_vendor_test. É possível usar uma interface
@VintfStability sem esses requisitos chamando AIBinder_forceDowngradeToLocalStability no back-end do NDK,
android::Stability::forceDowngradeToLocalStability no back-end do C++ ou android.os.Binder#forceDowngradeToSystemStability no back-end do Java
em um objeto binder antes que ele seja enviado para outro processo.
Além disso, para maximizar a portabilidade do código e evitar possíveis problemas, como bibliotecas adicionais desnecessárias, desative o back-end CPP.
O código mostra como desativar o back-end do CPP:
aidl_interface: {
...
backend: {
cpp: {
enabled: false,
},
},
}
Encontrar interfaces HAL AIDL
As interfaces AIDL estáveis do AOSP para HALs estão em pastas aidl nos mesmos
diretórios de base das interfaces HIDL:
hardware/interfacesé para interfaces normalmente fornecidas por hardware.frameworks/hardware/interfacesé para interfaces de alto nível fornecidas ao hardware.system/hardware/interfacesé para interfaces de baixo nível fornecidas ao hardware.
Coloque interfaces de extensão em outros subdiretórios hardware/interfaces em vendor ou hardware.
Interfaces de extensão
O Android tem um conjunto de interfaces oficiais do AOSP em cada lançamento. Quando os parceiros do Android querem adicionar recursos a essas interfaces, eles não devem mudar diretamente, porque isso torna o tempo de execução do Android incompatível com o tempo de execução do Android AOSP. Evite mudar essas interfaces para que a imagem GSI continue funcionando.
As extensões podem se registrar de duas maneiras diferentes:
- No tempo de execução. Consulte Interfaces de extensão anexadas.
- Como um serviço independente, registrado globalmente e no VINTF
No entanto, quando componentes específicos do fornecedor (ou seja, não fazem parte do AOSP upstream) usam a interface, não é possível ter conflitos de mesclagem. No entanto, quando modificações downstream são feitas em componentes do AOSP upstream, podem ocorrer conflitos de mesclagem, e as seguintes estratégias são recomendadas:
- Faça o upstream das adições de interface para o AOSP na próxima versão.
- Adições à interface upstream que permitem mais flexibilidade (sem conflitos de mesclagem) na próxima versão.
Parcelables de extensão: ParcelableHolder
ParcelableHolder é uma instância da interface Parcelable que pode
conter outra instância de Parcelable.
O principal caso de uso do ParcelableHolder é tornar o Parcelable extensível.
Por exemplo, imagine que os implementadores de dispositivos esperam poder estender um
Parcelable e AospDefinedParcelable definidos pelo AOSP para incluir recursos
de valor agregado.
Use a interface ParcelableHolder para estender Parcelable com seus recursos de valor agregado. A interface ParcelableHolder contém uma instância de Parcelable. Se você tentar adicionar campos diretamente a Parcelable, isso
causará um erro:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Como visto no código anterior, essa prática é inadequada porque os campos
adicionados pelo implementador do dispositivo podem ter um conflito quando Parcelable for
revisado nas próximas versões do Android.
Usando ParcelableHolder, o proprietário de um parcelable pode definir um ponto
de extensão em uma instância de Parcelable:
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Em seguida, os implementadores de dispositivos podem definir a própria instância Parcelable para
a extensão:
parcelable OemDefinedParcelable {
String x;
int[] y;
}
A nova instância Parcelable pode ser anexada à Parcelable original com o campo ParcelableHolder:
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Nomes de instâncias do servidor HAL AIDL
Por convenção, os serviços AIDL HAL têm um nome de instância no formato
$package.$type/$instance. Por exemplo, uma instância do HAL do vibrador é
registrada como android.hardware.vibrator.IVibrator/default.
Escrever um servidor HAL AIDL
@VintfStability Os servidores AIDL precisam ser declarados no manifesto VINTF, por exemplo:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
Caso contrário, eles precisam registrar um serviço AIDL normalmente. Ao executar testes do VTS, espera-se que todos os HALs AIDL declarados estejam disponíveis.
Escrever um cliente AIDL
Os clientes AIDL precisam se declarar na matriz de compatibilidade, por exemplo:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Converter uma HAL de HIDL para AIDL
Use a ferramenta hidl2aidl para converter uma interface HIDL em AIDL.
Recursos do hidl2aidl:
- Crie arquivos AIDL (
.aidl) com base nos arquivos HAL (.hal) para o pacote especificado. - Crie regras de build para o pacote AIDL recém-criado com todos os backends ativados.
- Crie métodos de tradução nos back-ends Java, CPP e NDK para traduzir dos tipos HIDL para os tipos AIDL.
- Crie regras de build para bibliotecas de tradução com as dependências necessárias.
- Crie declarações estáticas para garantir que os enumeradores HIDL e AIDL tenham os mesmos valores nos back-ends CPP e NDK.
Siga estas etapas para converter um pacote de arquivos HAL em arquivos AIDL:
Crie a ferramenta localizada em
system/tools/hidl/hidl2aidl.Criar essa ferramenta com base na fonte mais recente oferece a experiência mais completa. Use a versão mais recente para converter interfaces em ramificações mais antigas de versões anteriores:
m hidl2aidlExecute a ferramenta com um diretório de saída seguido pelo pacote a ser convertido.
Se quiser, use o argumento
-lpara adicionar o conteúdo de um novo arquivo de licença à parte de cima de todos os arquivos gerados. Use a licença e a data corretas:hidl2aidl -o <output directory> -l <file with license> <package>Exemplo:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2Leia os arquivos gerados e corrija os problemas com a conversão:
conversion.logcontém problemas não tratados que precisam ser corrigidos primeiro.- Os arquivos AIDL gerados podem ter avisos e sugestões que
precisam de ação. Esses comentários começam com
//. - Limpe e faça melhorias no pacote.
- Verifique a anotação
@JavaDerivepara recursos que podem ser necessários, comotoStringouequals.
Crie apenas os destinos necessários:
- Desative os back-ends que não serão usados. Prefira o back-end do NDK em vez do back-end do CPP. Consulte Criar com base no tempo de execução da AIDL.
- Remova as bibliotecas de tradução ou qualquer código gerado que não será usado.
Consulte Principais diferenças entre AIDL e HIDL:
- O uso do
Statuse das exceções integrados do AIDL geralmente melhora a interface e elimina a necessidade de outro tipo de status específico da interface. - Os argumentos da interface AIDL em métodos não são
@nullablepor padrão, como eram no HIDL.
- O uso do
SEPolicy para HALs da AIDL
Um tipo de serviço AIDL visível para o código do fornecedor precisa ter o atributo
hal_service_type. Caso contrário, a configuração da sepolicy é a mesma
de qualquer outro serviço AIDL (embora haja atributos especiais para HALs). Confira um exemplo de definição de um contexto de serviço HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Para a maioria dos serviços definidos pela plataforma, um contexto de serviço com o tipo correto já é adicionado. Por exemplo, android.hardware.foo.IFoo/default já está marcado como hal_foo_service. No entanto, se um cliente de framework oferecer suporte a vários nomes de instância, outros nomes precisarão ser adicionados em arquivos service_contexts específicos do dispositivo:
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Ao criar um novo tipo de HAL, é necessário adicionar atributos a ele. Um atributo específico da HAL pode ser associado a vários tipos de serviço, cada um com várias instâncias, como discutido acima. Para uma HAL, foo, há
hal_attribute(foo). Essa macro define os atributos hal_foo_client e hal_foo_server. Para um determinado domínio, as macros hal_client_domain e hal_server_domain associam um domínio a um determinado atributo HAL. Por
exemplo, o servidor do sistema ser um cliente desse HAL corresponde à política
hal_client_domain(system_server, hal_foo). Um servidor HAL também inclui
hal_server_domain(my_hal_domain, hal_foo).
Normalmente, para um determinado atributo HAL, também crie um domínio como
hal_foo_default para HALs de referência ou exemplo. No entanto, alguns dispositivos usam esses domínios para os próprios servidores. Distinguir entre domínios para vários servidores só é importante se houver vários servidores que atendem à mesma interface e precisam de um conjunto de permissões diferente nas implementações.
Em todas essas macros, hal_foo não é um objeto sepolicy. Em vez disso, esse token é usado por essas macros para se referir ao grupo de atributos associados a um par de servidor cliente.
No entanto, até agora, hal_foo_service e hal_foo (o par de atributos de hal_attribute(foo)) não estão associados. Um atributo HAL é associado
a serviços HAL AIDL usando a macro hal_attribute_service (HALs HIDL usam
a macro hal_attribute_hwservice), por exemplo,
hal_attribute_service(hal_foo, hal_foo_service). Isso significa que os processos hal_foo_client podem acessar a HAL, e os processos hal_foo_server podem registrar a HAL. A aplicação dessas regras de registro é feita pelo gerenciador de contexto (servicemanager).
Os nomes de serviço nem sempre correspondem aos atributos de HAL, por exemplo,
hal_attribute_service(hal_foo, hal_foo2_service). Em geral, como isso implica que os serviços são sempre usados juntos, você pode remover o hal_foo2_service e usar hal_foo_service para todos os contextos de serviço. Quando as HALs definem várias instâncias hal_attribute_service, é porque
o nome do atributo HAL original não é geral o suficiente e não pode ser mudado.
Juntando tudo isso, um exemplo de HAL fica assim:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Interfaces de extensão anexadas
Uma extensão pode ser anexada a qualquer interface de binder, seja uma interface de nível superior registrada diretamente com o gerenciador de serviços ou uma subinterface. Ao receber uma extensão, confirme se o tipo dela é o esperado. Só é possível definir extensões no processo que atende a um binder.
Use extensões anexadas sempre que uma extensão modificar a funcionalidade de um HAL existente. Quando uma capacidade totalmente nova é necessária, esse mecanismo não é necessário, e você pode registrar uma interface de extensão diretamente com o gerenciador de serviços. As interfaces de extensão anexadas fazem mais sentido quando estão anexadas a subinterfaces, porque essas hierarquias podem ser profundas ou multiinstanciadas. Usar uma extensão global para espelhar a hierarquia de interface do binder de outro serviço exige uma contabilidade extensa para fornecer recursos equivalentes às extensões anexadas diretamente.
Para definir uma extensão em um binder, use as seguintes APIs:
- Back-end do NDK:
AIBinder_setExtension - Back-end Java:
android.os.Binder.setExtension - Back-end do CPP:
android::Binder::setExtension - Back-end em Rust:
binder::Binder::set_extension
Para receber uma extensão em um binder, use as seguintes APIs:
- Back-end do NDK:
AIBinder_getExtension - Back-end Java:
android.os.IBinder.getExtension - Back-end do CPP:
android::IBinder::getExtension - Back-end em Rust:
binder::Binder::get_extension
Você encontra mais informações sobre essas APIs na documentação da função
getExtension no back-end correspondente. Um exemplo de como usar
extensões está em
hardware/interfaces/tests/extension/vibrator.
Principais diferenças entre AIDL e HIDL
Ao usar HALs ou interfaces AIDL, fique atento às diferenças em comparação com a escrita de HALs HIDL.
- A sintaxe da linguagem AIDL é mais parecida com a do Java. A sintaxe do HIDL é semelhante à do C++.
- Todas as interfaces AIDL têm status de erro integrados. Em vez de criar tipos de status personalizados, crie ints de status constantes em arquivos de interface e use
EX_SERVICE_SPECIFICnos back-ends CPP e NDK eServiceSpecificExceptionno back-end Java. Consulte Tratamento de erros. - A AIDL não inicia automaticamente pools de linhas de execução quando objetos binder são enviados. Você precisa iniciá-las manualmente (consulte Gerenciamento de linhas de execução).
- A AIDL não é interrompida em erros de transporte não verificados (a HIDL
Returné interrompida em erros não verificados). - A AIDL só pode declarar um tipo por arquivo.
- Os argumentos da AIDL podem ser especificados como
in,outouinout, além do parâmetro de saída (não há callbacks síncronos). - A AIDL usa
fdcomo o tipo primitivo em vez dehandle. - O HIDL usa versões principais para mudanças incompatíveis e versões secundárias para
mudanças compatíveis. Na AIDL, as mudanças compatíveis com versões anteriores são feitas no lugar.
A AIDL não tem um conceito explícito de versões principais. Em vez disso, isso é
incorporado aos nomes de pacotes. Por exemplo, a AIDL pode usar o nome do pacote
bluetooth2. - A AIDL não herda a prioridade em tempo real por padrão. A função
setInheritRtprecisa ser usada por binder para ativar a herança de prioridade em tempo real.
Testes para HALs
Esta seção descreve as práticas recomendadas para testar HALs. Essas práticas são válidas mesmo que o teste de integração do seu HAL não esteja no VTS.
O Android depende do VTS para verificar as implementações de HAL esperadas. O VTS ajuda a garantir que o Android seja compatível com versões anteriores de implementações antigas de fornecedores. Implementações que não passam no VTS têm problemas de compatibilidade conhecidos que podem impedir o funcionamento delas com versões futuras do SO.
Há duas partes principais do VTS para HALs.
1. Verificar se as HALs no dispositivo são conhecidas e esperadas pelo Android
O Android depende de uma lista estática e precisa de todos os HALs instalados. Essa lista é expressa no manifesto VINTF. Testes especiais em toda a plataforma verificam a integridade das camadas HAL em todo o sistema. Antes de gravar testes específicos da HAL, execute também estes testes, porque eles podem informar se uma HAL tem configurações VINTF inconsistentes.
Esse conjunto de testes pode ser encontrado em
test/vts-testcase/hal/treble/vintf. Se você estiver trabalhando em uma implementação de HAL
do fornecedor, use vts_treble_vintf_vendor_test para verificar. É possível executar
esse teste com o comando atest vts_treble_vintf_vendor_test.
Esses testes são responsáveis por verificar:
- Cada interface
@VintfStabilitydeclarada em um manifesto VINTF é congelada em uma versão lançada conhecida. Isso verifica se os dois lados da interface concordam com a definição exata dessa versão. Isso é necessário para a operação básica. - Todas as HALs declaradas em um manifesto VINTF estão disponíveis no dispositivo. Qualquer cliente com permissões suficientes para usar um serviço HAL declarado precisa poder receber e usar esses serviços a qualquer momento.
- Todas as HALs declaradas em um manifesto VINTF estão veiculando a versão da interface que elas declaram no manifesto.
- Não há HALs obsoletos sendo veiculados em um dispositivo. O Android descontinua o suporte para versões anteriores de interfaces HAL, conforme descrito em Ciclo de vida do FCM.
- Os HALs necessários estão presentes no dispositivo. Algumas HALs são necessárias para que o Android funcione corretamente.
2. Verifique o comportamento esperado de cada HAL.
Cada interface HAL tem os próprios testes do VTS para verificar o comportamento esperado dos clientes. Os casos de teste são executados em todas as instâncias de uma interface HAL declarada e impõem um comportamento específico com base na versão da interface implementada.
Em C++, é possível receber uma lista de todas as HALs instaladas no sistema com a função
android::getAidlHalInstanceNames em libaidlvintf_gtest_helper. Em Rust, use binder::get_declared_instances.
Esses testes tentam cobrir todos os aspectos da implementação da HAL em que o framework Android se baseia ou pode se basear no futuro.
Esses testes incluem a verificação do suporte a recursos, o tratamento de erros e qualquer outro comportamento que um cliente possa esperar do serviço.
Marcos do VTS para desenvolvimento de HAL
Espera-se que os testes VTS (ou qualquer teste) sejam mantidos atualizados ao criar ou modificar as interfaces HAL do Android.
Os testes do VTS precisam ser concluídos e estar prontos para verificar as implementações do fornecedor antes de serem congelados para lançamentos da API do fornecedor do Android. Elas precisam estar prontas antes do congelamento das interfaces para que os desenvolvedores possam criar implementações, verificá-las e dar feedback aos desenvolvedores da interface HAL.
Testar no Cuttlefish
Quando o hardware não está disponível, o Android usa o Cuttlefish como um veículo de desenvolvimento para interfaces HAL. Isso permite testes de integração escalonáveis do Android.
O hal_implementation_test testa se o Cuttlefish tem implementações das
versões mais recentes da interface HAL para garantir que o Android esteja pronto para lidar com
as novas interfaces e que os testes VTS estejam prontos para testar as novas
implementações do fornecedor assim que novos hardware e dispositivos estiverem disponíveis.