AIDL para HALs

O Android 11 apresenta a capacidade de usar AIDL para HALs no Android. Isso torna 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, HIDL deve ser usado).

HALs que usam AIDL para comunicação entre componentes da estrutura, como aqueles em system.img e componentes de hardware, como aqueles em vendor.img , devem usar AIDL estável. No entanto, para comunicar dentro de uma partição, por exemplo, de um HAL para outro, não há restrição quanto ao mecanismo IPC a ser usado.

Motivação

O AIDL existe há mais tempo que o HIDL e é usado em muitos outros lugares, como entre componentes da estrutura Android ou em aplicativos. Agora que AIDL tem suporte de estabilidade, é possível implementar uma pilha inteira com um único tempo de execução IPC. AIDL também possui um sistema de versionamento melhor que o HIDL.

  • Usar uma única linguagem IPC significa ter apenas uma coisa para aprender, depurar, otimizar e proteger.
  • AIDL suporta versionamento local para os proprietários de uma interface:
    • Os proprietários podem adicionar métodos ao final das interfaces ou campos aos parcelables. Isso significa que é mais fácil versionar o código ao longo dos anos e também que o custo ano após 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 em tempo de execução em vez de no sistema de tipos, portanto, não há necessidade de rebase de extensões downstream em versões mais recentes de interfaces.
  • Uma interface AIDL existente pode ser usada diretamente quando seu proprietário decidir estabilizá-la. Antes, uma cópia inteira da interface teria que ser criada em HIDL.

Escrevendo uma interface AIDL HAL

Para que uma interface AIDL seja usada entre o sistema e o fornecedor, a interface precisa de duas alterações:

  • Cada definição de tipo deve ser anotada com @VintfStability .
  • A declaração aidl_interface precisa incluir stability: "vintf", .

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

Ao fazer essas alterações, a interface deve 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 backend NDK, android::Stability::forceDowngradeToLocalStability no backend C++ ou android.os.Binder#forceDowngradeToSystemStability no backend Java em um objeto binder antes de ser enviado para outro processo. O downgrade de um serviço para estabilidade do fornecedor não é suportado em Java porque todos os aplicativos são executados em um contexto de sistema.

Além disso, para máxima portabilidade de código e para evitar possíveis problemas, como bibliotecas adicionais desnecessárias, desative o back-end do CPP.

Observe que o uso de backends no exemplo de código abaixo está correto, pois existem três back-ends (Java, NDK e CPP). O código abaixo explica como selecionar especificamente o backend CPP para desativá-lo.

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

Encontrando interfaces AIDL HAL

As interfaces AOSP Stable AIDL para HALs estão nos mesmos diretórios base das interfaces HIDL, em pastas aidl .

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

Você deve colocar interfaces de extensão em outros subdiretórios hardware/interfaces em vendor ou hardware .

Interfaces de extensão

O Android possui um conjunto de interfaces AOSP oficiais em cada versão. Quando os parceiros Android desejam adicionar funcionalidades a essas interfaces, eles não devem alterá-las diretamente, pois isso significaria que o tempo de execução do Android é incompatível com o tempo de execução do Android AOSP. Para dispositivos GMS, evitar alterar essas interfaces também é o que garante que a imagem GSI possa continuar funcionando.

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

  • em tempo de execução, consulte extensões anexadas .
  • autônomo, registrado globalmente e no VINTF.

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

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

Parceláveis ​​de extensão: ParcelableHolder

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

Anteriormente, 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
}

Como visto no código anterior, esta prática é quebrada porque os campos adicionados pelo implementador do dispositivo podem entrar em conflito quando o 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 um Parcelable .

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

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

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

Finalmente, o novo Parcelable pode ser anexado ao Parcelable original através do 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>();

Construindo no tempo de execução AIDL

AIDL tem três back-ends diferentes: Java, NDK, CPP. Para usar o Stable AIDL, você deve sempre usar a cópia do sistema do libbinder em system/lib*/libbinder.so e falar em /dev/binder . Para código na imagem do fornecedor, isso significa que libbinder (do VNDK) não pode ser usado: esta biblioteca tem uma API C++ instável e componentes internos instáveis. Em vez disso, o código nativo do fornecedor deve usar o back-end NDK do AIDL, vincular-se libbinder_ndk (que é apoiado pelo sistema libbinder.so ) e vincular-se às bibliotecas -ndk_platform criadas pelas entradas aidl_interface .

Nomes de instância do servidor AIDL HAL

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 vibrador HAL é registrada como android.hardware.vibrator.IVibrator/default .

Escrevendo um servidor AIDL HAL

Os servidores AIDL @VintfStability devem ser declarados no manifesto VINTF, por exemplo assim:

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

Caso contrário, eles deverão registrar um serviço AIDL normalmente. Ao executar testes VTS, espera-se que todos os HALs AIDL declarados estejam disponíveis.

Escrevendo um cliente AIDL

Os clientes AIDL devem se declarar na matriz de compatibilidade, por exemplo assim:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Convertendo um HAL existente de HIDL para AIDL

Use a ferramenta hidl2aidl para converter uma interface HIDL em AIDL.

Recursos hidl2aidl :

  • Crie arquivos .aidl com base nos arquivos .hal do pacote fornecido
  • Crie regras de compilação para o pacote AIDL recém-criado com todos os back-ends habilitados
  • 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 construção 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:

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

    Construir esta ferramenta a partir da fonte mais recente fornece 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 do pacote a ser convertido.

    Opcionalmente, use o argumento -l para adicionar o conteúdo de um novo arquivo de licença ao topo de todos os arquivos gerados. Certifique-se de usar 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 quaisquer problemas com a conversão.

    • conversion.log contém quaisquer problemas não tratados para corrigir primeiro.
    • Os arquivos .aidl gerados podem conter avisos e sugestões que podem precisar de ação. Esses comentários começam com // .
    • Aproveite para fazer uma limpeza e fazer melhorias na embalagem.
    • Verifique a anotação @JavaDerive para recursos que possam ser necessários, como toString ou equals .
  4. Crie apenas os alvos que você precisa.

    • Desative back-ends que não serão usados. Prefira o back-end do NDK ao back-end do CPP; consulte como escolher o tempo de execução .
    • Remova bibliotecas de tradução ou qualquer código gerado que não será usado.
  5. Consulte Principais diferenças AIDL/HIDL .

    • O uso do Status e das exceções integrados do 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 eram no HIDL.

Sepolicy para AIDL HALs

Um tipo de serviço AIDL visível para o código do fornecedor deve ter o atributo hal_service_type . Caso contrário, a configuração da sepolicy é a mesma de qualquer outro serviço AIDL (embora existam atributos especiais para HALs). Aqui está 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á estaria marcado como hal_foo_service ). No entanto, se um cliente de estrutura suportar vários nomes de instância, nomes de instância adicionais deverão ser adicionados em arquivos service_contexts específicos do dispositivo.

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

Os atributos HAL devem ser adicionados quando criamos um novo tipo de HAL. Um atributo HAL específico pode estar associado a vários tipos de serviço (cada um dos quais pode ter múltiplas instâncias, como acabamos de discutir). Para um HAL, foo , temos hal_attribute(foo) . Esta 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 deste HAL corresponde à política hal_client_domain(system_server, hal_foo) . Um servidor HAL inclui de forma semelhante hal_server_domain(my_hal_domain, hal_foo) . Normalmente, para um determinado atributo HAL, também criamos um domínio como hal_foo_default para referência ou exemplo de HALs. No entanto, alguns dispositivos utilizam estes domínios para os seus próprios servidores. A distinção entre domínios para vários servidores só importa se tivermos vários servidores que servem a mesma interface e precisam de um conjunto de permissões diferente em suas implementações. Em todas essas macros, hal_foo não é realmente um objeto sepolicy. Em vez disso, esse token é usado por essas macros para se referir ao grupo de atributos associados a um par cliente-servidor.

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 aos 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 obter o HAL e os processos hal_foo_server podem registrar o HAL. A aplicação destas regras de registro é feita pelo gerenciador de contexto ( servicemanager ). Observe que os nomes dos serviços nem sempre correspondem aos atributos HAL. Por exemplo, podemos ver hal_attribute_service(hal_foo, hal_foo2_service) . Geralmente, porém, como isso implica que os serviços são sempre usados ​​juntos, poderíamos considerar remover o hal_foo2_service e usar hal_foo_service para todos os nossos contextos de serviço. A maioria dos HALs que definem vários hal_attribute_service ocorre porque o nome do atributo HAL original não é geral o suficiente e não pode ser alterado.

Juntando tudo isso, um exemplo de HAL se parece com este:

    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 do fichário, seja uma interface de nível superior registrada diretamente no gerenciador de serviços ou uma subinterface. Ao obter uma extensão, você deve confirmar se o tipo de extensão é o esperado. As extensões só podem ser definidas a partir do processo que atende um fichário.

Extensões anexadas devem ser usadas sempre que uma extensão modificar a funcionalidade de um HAL existente. Quando uma funcionalidade totalmente nova é necessária, esse mecanismo não precisa ser usado e uma interface de extensão pode ser registrada 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 com múltiplas instâncias. Usar uma extensão global para espelhar a hierarquia da interface do fichário de outro serviço exigiria uma extensa contabilidade para fornecer funcionalidade equivalente às extensões diretamente anexadas.

Para definir uma extensão no fichário, use as seguintes APIs:

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

Para obter uma extensão em um fichário, use as seguintes APIs:

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

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

Principais diferenças AIDL/HIDL

Ao usar AIDL HALs ou interfaces AIDL HAL, esteja ciente das diferenças em comparação com a gravação de HALs HIDL.

  • A sintaxe da linguagem AIDL está mais próxima do Java. A sintaxe HIDL é semelhante a C++.
  • Todas as interfaces AIDL possuem status de erro integrados. Em vez de criar tipos de status personalizados, crie entradas de status constantes em arquivos de interface e use EX_SERVICE_SPECIFIC nos back-ends CPP/NDK e ServiceSpecificException no back-end Java. Consulte Tratamento de erros .
  • AIDL não inicia automaticamente threadpools quando objetos binder são enviados. Eles devem ser iniciados manualmente (consulte gerenciamento de threads ).
  • AIDL não aborta em erros de transporte não verificados (HIDL Return aborta em erros não verificados).
  • AIDL só pode declarar um tipo por arquivo.
  • Os argumentos AIDL podem ser especificados como in/out/inout além do parâmetro de saída (não há "retornos de chamada síncronos").
  • AIDL usa um fd como tipo primitivo em vez de handle.
  • HIDL usa versões principais para alterações incompatíveis e versões secundárias para alterações compatíveis. No AIDL, alterações compatíveis com versões anteriores são feitas no local. AIDL não tem nenhum conceito explícito de versões principais; em vez disso, isso é incorporado aos nomes dos pacotes. Por exemplo, AIDL pode usar o nome do pacote bluetooth2 .
  • AIDL não herda prioridade em tempo real por padrão. A função setInheritRt deve ser usada por fichário para permitir a herança de prioridade em tempo real.