O Android 11 apresenta a capacidade de usar a AIDL para HALs no Android, tornando possível implementar partes do Android sem HIDL. Transição de HALs para usar AIDL exclusivamente sempre que possível (quando HALs upstream usam HIDL, ela precisa ser usada).
As HALs que usam a AIDL para se comunicar entre componentes do framework, como as do
system.img
, e componentes de hardware, como os do vendor.img
, precisam usar
a AIDL estável. No entanto, para se comunicar em uma partição, por exemplo, de um
HAL para outro, não há restrição ao mecanismo de IPC a ser usado.
Motivação
A AIDL existe há mais tempo que a HIDL e é usada em muitos outros lugares, como entre componentes do framework do Android ou em apps. Agora que a AIDL tem suporte à estabilidade, é possível implementar uma pilha inteira com um único ambiente de execução de IPC. O AIDL também tem um sistema de controle de versão melhor do que o HIDL. Confira algumas vantagens do AIDL:
- Usar uma única linguagem IPC significa ter apenas uma coisa para aprender, depurar, otimizar e proteger.
- A AIDL oferece suporte ao controle de versões no local para os proprietários de uma interface:
- Os proprietários podem adicionar métodos ao final das interfaces ou campos a parceláveis. Isso significa que é mais fácil fazer a versão do código ao longo dos anos, e também o custo anual é menor (os tipos podem ser alterados no local e não há necessidade de bibliotecas extras para cada versão da interface).
- As interfaces de extensão podem ser anexadas no momento da execução, e não no sistema de tipo, então não é necessário refazer as extensões downstream em versões mais recentes das interfaces.
- Uma interface AIDL existente pode ser usada diretamente quando o proprietário decide estabilizá-la. Antes, uma cópia inteira da interface precisava ser criada em HIDL.
Criar com base no ambiente de execução da AIDL
O 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 recursos internos instáveis. Em vez disso, o código do fornecedor nativo precisa usar o back-end
do NDK da AIDL, vincular-se a libbinder_ndk
(que é respaldado pelo
libbinder.so
do sistema) e vincular-se às bibliotecas do NDK criadas por entradas
aidl_interface
. Para saber os nomes exatos dos módulos, consulte Regras de nomeação
de módulos.
Gravar uma interface AIDL HAL
Para que uma interface AIDL seja usada entre o sistema e o fornecedor, ela precisa fazer duas mudanças:
- Cada definição de tipo precisa ser anotada com
@VintfStability
. - A declaração
aidl_interface
precisa incluirstability: "vintf",
.
Somente 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 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 de vinculação antes de ser enviado para outro processo.
Além disso, para a máxima portabilidade de código e para evitar possíveis problemas, como bibliotecas adicionais desnecessárias, desative o back-end do CPP.
O uso de backends
no exemplo de código abaixo está correto, já que
há três back-ends (Java, NDK e CPP). O código mostra como
desativar um back-end de CPP:
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Encontrar interfaces HAL AIDL
As interfaces AIDL estáveis do AOSP para HALs estão nas pastas aidl
nos mesmos
diretórios base das interfaces HIDL:
hardware/interfaces
é para interfaces normalmente fornecidas pelo 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 versão. Quando os parceiros do Android quiserem adicionar recursos a essas interfaces, eles não poderão mudar essas diretamente, porque isso torna o ambiente de execução do Android incompatível com o ambiente de execução do Android AOSP. Evite mudar essas interfaces para que a imagem do GSI continue funcionando.
As extensões podem se registrar de duas maneiras:
- No momento da execução. Consulte Interfaces de extensão anexadas.
- Como um recurso independente, registrado globalmente e no VINTF
Não é possível fazer conflitos de mesclagem quando uma extensão é registrada. Quando componentes específicos do fornecedor (ou seja, que não fazem parte do AOSP upstream) usam a interface. No entanto, quando modificações downstream em componentes AOSP upstream são feitas, conflitos de mesclagem podem ocorrer, e as seguintes estratégias são recomendadas:
- As adições de interface serão enviadas para o AOSP na próxima versão.
- Adições de 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 de ParcelableHolder
é tornar o Parcelable
extensível.
Por exemplo, imagine que os implementadores de dispositivos esperam poder estender um
Parcelable
definido pelo AOSP, AospDefinedParcelable
, para incluir os recursos de valor agregado
deles.
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 a Parcelable
diretamente, ocorrerá um erro:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Como mostrado no código anterior, essa prática está incorreta porque os campos
adicionados pelo implementador do dispositivo podem ter um conflito quando Parcelable
for
revisada 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 de 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 AIDL HAL
Por convenção, os serviços HAL do AIDL 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
.
Gravar um servidor HAL AIDL
Os servidores AIDL @VintfStability
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, é esperado que todos os HALs AIDL declarados estejam disponíveis.
Programar 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. - Crie regras de build para o pacote AIDL recém-criado com todos os back-ends 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 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 a versão mais recente oferece a experiência mais completa. É possível usar a versão mais recente para converter interfaces em versões anteriores:
m hidl2aidl
Execute a ferramenta com um diretório de saída seguido pelo pacote a ser convertido.
Como alternativa, use o argumento
-l
para 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.2
Leia os arquivos gerados e corrija os problemas com a conversão:
conversion.log
conté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
@JavaDerive
para recursos que podem ser necessários, comotoString
ouequals
.
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 CPP. Consulte Criar para o ambiente 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 de
Status
e exceções integradas do AIDL normalmente 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
@nullable
por padrão, como era em HIDL.
- O uso de
SEPolicy para HALs da AIDL
Um tipo de serviço AIDL que é visível para o código do fornecedor precisa ter o
atributo hal_service_type
. Caso contrário, a configuração de 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á foi 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 de instância 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 HAL. Um atributo HAL
específico pode ser associado a vários tipos de serviço, cada um deles pode
ter várias instâncias, conforme discutido. Para um 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 sendo 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 exemplo ou de referência. No entanto, alguns dispositivos usam
esses domínios para os próprios servidores. A distinção entre domínios para
vários servidores é importante apenas 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 do 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 de 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 o HAL, e os processos hal_foo_server
podem registrar o HAL. A aplicação dessas regras de registro é
feita pelo gerenciador de contexto (servicemanager
).
Os nomes de serviço nem sempre correspondem aos atributos 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, é possível remover
o hal_foo2_service
e usar hal_foo_service
para todos os contextos
de serviço. Quando os HALs definem várias instâncias de hal_attribute_service
, isso ocorre porque
o nome do atributo HAL original não é suficientemente geral e não pode ser alterado.
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 vinculação, seja uma interface de nível superior registrada diretamente no gerenciador de serviços ou uma subinterface. Ao receber uma extensão, é necessário confirmar se o tipo dela é o esperado. É possível definir extensões apenas do processo que serve um vinculador.
Use extensões anexadas sempre que uma extensão modificar a funcionalidade de um HAL existente. Quando um recurso totalmente novo é necessário, 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 são anexadas a subinterfaces, porque essas hierarquias podem ser profundas ou ter várias instâncias. O uso de uma extensão global para espelhar a hierarquia da interface de vinculação de outro serviço requer uma contabilidade extensa para fornecer recursos equivalentes às extensões anexadas diretamente.
Para definir uma extensão em um vinculador, use as seguintes APIs:
- Back-end do NDK:
AIBinder_setExtension
- Back-end Java:
android.os.Binder.setExtension
- Endpoints de backend do CPP:
android::Binder::setExtension
- Endpoint de 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
- Endpoints de backend do CPP:
android::IBinder::getExtension
- Executor de Rust:
binder::Binder::get_extension
Você pode encontrar 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 AIDL ou interfaces HAL AIDL, esteja ciente das diferenças em comparação com a criação de HALs HIDL.
- A sintaxe da linguagem AIDL é mais próxima do Java. A sintaxe de 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_SPECIFIC
nos back-ends do CPP e do NDK eServiceSpecificException
no back-end Java. Consulte Tratamento de erros. - O AIDL não inicia automaticamente os pools de threads quando os objetos de vinculação são enviados. Você precisa iniciá-los manualmente (consulte Gerenciamento de linhas de execução).
- A AIDL não é interrompida em erros de transporte não verificados (a
Return
do HIDL é interrompida em erros não verificados). - O AIDL pode declarar apenas um tipo por arquivo.
- Os argumentos AIDL podem ser especificados como
in
,out
ouinout
, além do parâmetro de saída (não há callbacks síncronos). - O AIDL usa
fd
como 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.
O AIDL não tem um conceito explícito de versões principais. Em vez disso, ele é
incorporado aos nomes de pacotes. Por exemplo, o AIDL pode usar o nome do pacote
bluetooth2
. - O AIDL não herda a prioridade em tempo real por padrão. A função
setInheritRt
precisa ser usada por binder para ativar a herança de prioridade em tempo real.
Testes do pacote de testes de fornecedor (VTS) para HALs
O Android depende do Conjunto de teste de fornecedor (VTS) para verificar as implementações esperadas da HAL. O VTS ajuda a garantir que o Android seja compatível com versões anteriores de implementações antigas de fornecedores. As implementações que falham no VTS têm problemas de compatibilidade conhecidos que podem impedir que elas funcionem com versões futuras do SO.
Há duas partes principais dos VTS para HALs.
1. Verifique se os HALs no dispositivo são conhecidos e esperados pelo Android
Esse conjunto de testes pode ser encontrado em
test/vts-testcase/hal/treble/vintf
.
Esses testes são responsáveis por verificar:
- Todas as interfaces
@VintfStability
declaradas em um manifesto VINTF são congeladas em uma versão lançada conhecida. Isso garante que os dois lados da interface concordem com a definição exata dessa versão da interface. Isso é necessário para a operação básica. - Todos os HALs declarados em um manifesto VINTF estão disponíveis nesse dispositivo. Qualquer cliente com permissões suficientes para usar um serviço HAL declarado precisa conseguir acessar e usar esses serviços a qualquer momento.
- Todos os HALs declarados em um manifesto VINTF veiculam a versão da interface declarada no manifesto.
- Não há HALs descontinuados sendo atendidos em um dispositivo. O Android não oferece mais suporte a versões anteriores de interfaces HAL, conforme descrito no ciclo de vida do FCM.
- Os HALs necessários estão presentes no dispositivo. Alguns HALs são necessários para que o Android funcione corretamente.
2. Verificar o comportamento esperado de cada HAL
Cada interface HAL tem os próprios testes 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 aplicam um comportamento específico com base na versão da interface implementada.
Esses testes tentam abranger todos os aspectos da implementação do HAL que o framework do Android usa ou pode usar no futuro.
Esses testes incluem a verificação de suporte a recursos, gerenciamento de erros e qualquer outro comportamento que um cliente possa esperar do serviço.
Marcos do VTS para o desenvolvimento do HAL
Os testes VTS precisam ser atualizados ao criar ou modificar as interfaces HAL do Android.
Os testes do VTS precisam ser concluídos e prontos para verificar as implementações do fornecedor antes de serem congelados para as versões da API do fornecedor do Android. Elas precisam estar prontas antes que as interfaces sejam congeladas para que os desenvolvedores possam criar as implementações, verificá-las e fornecer feedback aos desenvolvedores da interface HAL.
VTS 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 VTs escalonáveis e testes de integração do Android.
Os testes hal_implementation_test
do Cuttlefish têm implementações das
versões mais recentes da interface HAL para garantir que o Android esteja pronto para processar
as novas interfaces e que os testes VTS estejam prontos para testar as novas implementações
do fornecedor assim que novos hardwares e dispositivos estiverem disponíveis.