AIDL para HALs

O Android 11 apresenta a capacidade de usar a AIDL para HALs no Android. Isso possibilita a implementação de partes do Android sem o HIDL. Faça a transição de HALs para usar a AIDL exclusivamente sempre que possível. Quando as HALs upstream usarem HIDL, ele precisará ser usado.

As HALs que usam AIDL para se comunicar entre componentes do framework, como os de system.img, e componentes de hardware, como os da vendor.img, precisam usar a AIDL estável. No entanto, para se comunicar dentro de uma partição, por exemplo, de uma HAL para outra, não há restrições de uso do mecanismo de IPC.

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. A AIDL também tem um sistema de controle de versões melhor do que o HIDL.

  • O uso de uma única linguagem de IPC significa ter apenas uma coisa para aprender, depurar, otimizar e proteger.
  • A AIDL oferece suporte a 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 para parcelables. Isso significa que é mais fácil criar o código de versão ao longo dos anos e o custo ano a ano é 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 ambiente de execução em vez de no sistema de tipos. Portanto, não é necessário realocar as extensões downstream em versões mais recentes de interfaces.
  • Uma interface AIDL atual pode ser usada diretamente quando o proprietário optar por estabilizar a interface. Antes, era preciso criar uma cópia inteira da interface no HIDL.

Criar no ambiente de execução da AIDL

A AIDL tem três back-ends diferentes: Java, NDK e CPP. Para usar a AIDL estável, use sempre a cópia do sistema do libbinder em system/lib*/libbinder.so e fale em /dev/binder. Para o código na imagem do fornecedor, 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 do fornecedor nativo precisa usar o back-end do NDK da AIDL, vincular a libbinder_ndk (que tem suporte do sistema libbinder.so) e vincular às bibliotecas do NDK criadas pelas entradas aidl_interface. Para saber os nomes exatos dos módulos, consulte as regras de nomenclatura dos módulos.

Criar uma interface HAL de AIDL

Para que uma interface AIDL seja usada entre o sistema e o fornecedor, a interface precisa de duas mudanças:

  • Cada definição de tipo precisa ter a anotação @VintfStability.
  • A declaração aidl_interface precisa incluir stability: "vintf",.

Somente o proprietário de uma interface pode fazer essas alterações.

Quando você faz essas alterações, a interface precisa estar no manifesto VINTF para funcionar. Teste isso e os requisitos relacionados, como verificar se as interfaces lançadas estão congeladas, usando o teste VTS vts_treble_vintf_vendor_test. Você pode usar uma interface @VintfStability sem esses requisitos chamando AIBinder_forceDowngradeToLocalStability no back-end do NDK, android::Stability::forceDowngradeToLocalStability no back-end C++ ou android.os.Binder#forceDowngradeToSystemStability no back-end Java em um objeto binder antes de enviar para outro processo. O Java não oferece suporte ao downgrade de um serviço para a estabilidade do fornecedor, porque todos os apps são executados em um contexto do sistema.

Além disso, para conseguir a portabilidade máxima de código e evitar possíveis problemas, como outras bibliotecas 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 abaixo informa como selecionar especificamente o back-end do CPP para desativá-lo.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

Encontrar interfaces HAL da AIDL

As interfaces AIDL estáveis do AOSP para HALs estão nos mesmos diretórios base que as interfaces HIDL, em pastas aidl.

  • hardware/interfaces
  • estruturas/hardware/interfaces
  • sistema/hardware/interfaces

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 querem adicionar funcionalidades a essas interfaces, não podem fazer mudanças diretamente, já que isso significa que o ambiente de execução do Android é incompatível com o do AOSP. Para dispositivos GMS, evitar a mudança dessas interfaces também é o que garante que a imagem GSI possa continuar funcionando.

As extensões podem ser registradas de duas maneiras diferentes:

  • durante a execução, consulte as extensões anexadas.
  • independente, registrada globalmente e no VINTF.

No entanto, quando uma extensão é registrada, quando componentes específicos do fornecedor (ou seja, que não fazem parte de upstream AOSP) usam a interface, não há possibilidade de conflito de mesclagem. No entanto, quando são feitas modificações downstream em componentes upstream do AOSP, podem ocorrer conflitos de mesclagem. As seguintes estratégias são recomendadas:

  • as adições de interface podem ser enviadas para o AOSP na próxima versão
  • as adições de interface que permitem mais flexibilidade, sem conflitos de mesclagem, podem ser enviadas

Parcelables da extensão: ParcelableHolder

ParcelableHolder é uma Parcelable que pode conter outra Parcelable. O principal caso de uso de ParcelableHolder é tornar um Parcelable extensível. Por exemplo, uma imagem que os implementadores de dispositivos esperam poder estender um Parcelable definido pelo AOSP, AospDefinedParcelable, para incluir os recursos de valor agregado.

Antes, sem ParcelableHolder, os implementadores de dispositivos não podiam modificar uma interface AIDL estável definida pelo AOSP porque seria um erro adicionar mais campos:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Conforme visto no código anterior, essa prática é corrompida porque os campos adicionados pelo implementador do dispositivo podem ter um conflito quando o Parcelable é revisado nas próximas versões do Android.

Ao usar ParcelableHolder, o proprietário de um parcelable pode definir um ponto de extensão em um Parcelable.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Em seguida, os implementadores do dispositivo podem definir o próprio Parcelable para a extensão.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Por fim, o novo Parcelable pode ser anexado ao 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 da AIDL

Por convenção, os serviços de HAL da AIDL têm um nome de instância no formato $package.$type/$instance. Por exemplo, uma instância da HAL de vibração é registrada como android.hardware.vibrator.IVibrator/default.

Criar um servidor de HAL da AIDL

Os servidores da AIDL @VintfStability precisam ser declarados no manifesto do VINTF, por exemplo assim:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

Caso contrário, eles devem registrar um serviço AIDL normalmente. Ao executar testes VTS, é esperado que todas as HALs da AIDL declaradas estejam disponíveis.

Criar um cliente AIDL

Os clientes da AIDL precisam se declarar na matriz de compatibilidade, por exemplo, desta forma:

    <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:

  • Criar arquivos .aidl com base nos arquivos .hal do pacote especificado
  • Crie regras de build para o pacote AIDL recém-criado com todos os back-ends ativados
  • Criar métodos de tradução nos back-ends Java, CPP e NDK para converter dos tipos HIDL para os tipos AIDL
  • Criar 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 de CPP e NDK

Siga estas etapas para converter um pacote de arquivos .hal em arquivos .aidl:

  1. Crie a ferramenta localizada em system/tools/hidl/hidl2aidl.

    Criar essa ferramenta com base na fonte mais recente oferece a experiência mais completa. Você pode usar a versão mais recente para converter interfaces em ramificações mais antigas de versões anteriores.

    m hidl2aidl
    
  2. Execute a ferramenta com um diretório de saída seguido pelo pacote a ser convertido.

    Opcionalmente, use o argumento -l para adicionar o conteúdo de um novo arquivo de licença à parte superior de todos os arquivos gerados. Use a licença e a data corretas.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Por exemplo:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Leia os arquivos gerados e corrija os problemas com a conversão.

    • conversion.log contém problemas não processados que precisam ser corrigidos primeiro.
    • Os arquivos .aidl gerados podem ter avisos e sugestões que podem precisar de ação. Esses comentários começam com //.
    • Aproveite para fazer uma limpeza e fazer melhorias no pacote.
    • Verifique a anotação @JavaDerive para recursos que podem ser necessários, como toString ou equals.
  4. 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 Como escolher o ambiente de execução.
    • Remova bibliotecas de tradução ou qualquer um dos códigos gerados que não serão usados.
  5. Consulte Principais diferenças de AIDL/HIDL.

    • O uso do Status integrado e das exceções da AIDL normalmente melhora a interface e elimina a necessidade de outro tipo de status específico da interface.
    • Os argumentos da interface AIDL nos métodos não são @nullable por padrão, como estavam no HIDL.

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 será igual a de qualquer outro serviço da AIDL, embora existam atributos especiais para HALs. Confira um exemplo de definição de contexto de serviço da 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á estaria marcado como hal_foo_service). No entanto, se um cliente do framework oferecer suporte a vários nomes de instância, outros nomes de instância precisarão ser adicionados aos arquivos service_contexts específicos do dispositivo.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Os atributos da HAL precisam ser adicionados ao criar um novo tipo de HAL. Um atributo HAL específico pode ser associado a vários tipos de serviço, e cada um deles pode ter várias instâncias, como acabamos de discutir. Para uma HAL, foo, temos 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, se o servidor do sistema for um cliente dessa HAL, ele corresponde à política hal_client_domain(system_server, hal_foo). Da mesma forma, um servidor de HAL inclui hal_server_domain(my_hal_domain, hal_foo). Normalmente, para um determinado atributo de HAL, também criamos um domínio como hal_foo_default para referência ou exemplo de HALs. No entanto, alguns dispositivos usam esses domínios nos próprios servidores. A distinção entre domínios para vários servidores só importa se temos vários servidores que servem a mesma interface e precisamos de um conjunto de permissões diferente nas implementações deles. Em todas essas macros, hal_foo não é, na verdade, um objeto sepolicy. Em vez disso, esse token é usado por essas macros para se referir ao grupo de atributos associados a um par de servidores de clientes.

No entanto, até agora, não associamos hal_foo_service e hal_foo (o par de atributos de hal_attribute(foo)). Um atributo HAL é associado a serviços da HAL da 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 registrá-la. A aplicação dessas regras de registro é feita pelo gerenciador de contexto (servicemanager). Observe que os nomes dos serviços nem sempre correspondem aos atributos da HAL. Por exemplo, podemos ver hal_attribute_service(hal_foo, hal_foo2_service). No entanto, em geral, como isso implica que os serviços são sempre usados juntos, podemos considerar remover o hal_foo2_service e usar hal_foo_service para todos os nossos contextos de serviço. A maioria das HALs que definem vários hal_attribute_service ocorrem porque o nome do atributo original da HAL não é geral o suficiente e não pode ser modificado.

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, você precisa confirmar se o tipo dela é o esperado. As extensões só podem ser definidas a partir do processo de exibição de um binder.

As extensões anexadas precisam ser usadas sempre que uma extensão modifica a funcionalidade de uma HAL existente. Quando é necessária uma funcionalidade totalmente nova, esse mecanismo não precisa ser usado, e uma interface de extensão pode ser registrada diretamente no 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 em várias instâncias. O uso de uma extensão global para espelhar a hierarquia da interface de vinculação de outro serviço exigiria uma extensa contabilidade para fornecer funcionalidade equivalente às extensões anexadas diretamente.

Para definir uma extensão no binder, use as seguintes APIs:

  • No back-end do NDK: AIBinder_setExtension
  • No back-end do Java: android.os.Binder.setExtension
  • No back-end do CPP: android::Binder::setExtension
  • No back-end do Rust: binder::Binder::set_extension

Para conseguir uma extensão em um binder, use as seguintes APIs:

  • No back-end do NDK: AIBinder_getExtension
  • No back-end do Java: android.os.IBinder.getExtension
  • No back-end do CPP: android::IBinder::getExtension
  • No back-end do Rust: binder::Binder::get_extension

Encontre mais informações sobre essas APIs na documentação da função getExtension no back-end correspondente. Um exemplo de como usar extensões pode ser encontrado em hardware/interfaces/tests/extension/vibrator.

Principais diferenças de AIDL e HIDL

Ao usar HALs da AIDL ou interfaces HAL da 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 do HIDL é semelhante ao 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/NDK e ServiceSpecificException no back-end Java. Consulte Tratamento de erros.
  • A AIDL não inicia pools de linhas de execução automaticamente quando os objetos de vinculação são enviados. Eles precisam ser iniciados manualmente. Consulte Gerenciamento de linhas de execução.
  • A AIDL não é cancelada em erros de transporte não verificados (HIDL Return é cancelado em erros não marcados).
  • A AIDL só pode declarar um tipo por arquivo.
  • Os argumentos da AIDL podem ser especificados como entrada/saída/entrada, além do parâmetro de saída. Não há "callbacks síncronos".
  • A AIDL usa um fd como tipo primitivo em vez de handle.
  • O HIDL usa versões principais para mudanças incompatíveis e versões secundárias para mudanças compatíveis. Na AIDL, são feitas alterações compatíveis com versões anteriores. A AIDL não tem um conceito explícito de versões principais. Em vez disso, ela é incorporada nos nomes dos pacotes. Por exemplo, a AIDL pode usar o nome de pacote bluetooth2.
  • A AIDL não herda a prioridade em tempo real por padrão. A função setInheritRt precisa ser usada por vinculação para ativar a herança de prioridade em tempo real.