AIDL para HAL

Android 11 introduce la capacidad de usar AIDL para HALs en Android. Esto hace que posible implementar partes de Android sin HIDL. HAL de transición para usar el AIDL exclusivamente cuando sea posible (cuando los HAL ascendentes usan HIDL, se debe usar el HIDL)

HAL que usan AIDL para comunicarse entre componentes del framework, como los de system.img y los componentes de hardware, como los de vendor.img, deben usar AIDL estable Sin embargo, para comunicarse dentro de una partición, por ejemplo, desde una HAL a otra, no hay restricciones en cuanto al mecanismo de IPC que se debe usar.

Motivación

El AIDL existe desde hace más tiempo que el HIDL y se usa en muchos otros lugares, como entre los componentes del framework de Android o las apps. Ahora que el AIDL tiene estabilidad es posible implementar una pila completa con un solo tiempo de ejecución de IPC. El AIDL también tiene un mejor sistema de control de versiones que el HIDL.

  • Usar un único lenguaje de IPC significa tener solo una cosa que aprender, depurar optimizar y proteger.
  • El AIDL admite el control de versiones local para los propietarios de una interfaz:
    • Los propietarios pueden agregar métodos al final de las interfaces o campos a los objetos parcelables. Esto significa que es más fácil cambiar las versiones del código a lo largo de los años el costo anual es menor (los tipos pueden modificarse localmente y no hay necesidad de bibliotecas adicionales para cada versión de la interfaz).
    • Las interfaces de extensión se pueden conectar durante el tiempo de ejecución, en lugar de en el tipo por lo que no es necesario reubicar las extensiones downstream versiones de interfaces.
  • Una interfaz de AIDL existente se puede usar directamente cuando el propietario elige para estabilizarlo. Antes, se debía tener una copia completa de la interfaz crearse en HIDL.

Compila en el entorno de ejecución de AIDL

El AIDL tiene tres backends diferentes: Java, NDK y CPP. Para usar AIDL estable, debes usar siempre la copia del sistema de libbinder en system/lib*/libbinder.so y hablar en /dev/binder. Para el código en la imagen del proveedor, significa que libbinder (del VNDK), ya que esta biblioteca tiene una API de C++ inestable y internas inestables. En su lugar, el código nativo del proveedor debe usar el backend del NDK de AIDL, vínculo con libbinder_ndk (que está respaldado por el sistema libbinder.so) y vincular con las bibliotecas del NDK que crean las entradas aidl_interface. Para los nombres exactos de los módulos, consulta reglas de nomenclatura de los módulos.

Escribe una interfaz de la HAL del AIDL

Para que se use una interfaz de AIDL entre el sistema y el proveedor, debe dos cambios:

  • Cada definición de tipo debe anotarse con @VintfStability.
  • La declaración aidl_interface debe incluir stability: "vintf",.

Solo el propietario de una interfaz puede realizar estos cambios.

Cuando realices estos cambios, la interfaz debe estar en manifiesto de VINTF para funcionar. Prueba esto (y los relacionados adicionales, como verificar que las interfaces actualizadas estén bloqueadas) con el Prueba de VTS vts_treble_vintf_vendor_test. Puedes usar un @VintfStability sin estos requisitos llamando a AIBinder_forceDowngradeToLocalStability en el backend del NDK, android::Stability::forceDowngradeToLocalStability en el backend de C++, o android.os.Binder#forceDowngradeToSystemStability en el backend de Java en un objeto vinculante antes de que se envíe a otro proceso. Cambia un servicio a una versión inferior con la estabilidad del proveedor no es compatible con Java porque todas las apps se ejecutan en un sistema adicional.

Además, para lograr la máxima portabilidad del código y evitar posibles problemas, como como bibliotecas adicionales innecesarias, inhabilita el backend de CPP.

Ten en cuenta que el uso de backends en el siguiente ejemplo de código es correcto, ya que se hay tres backends (Java, NDK y CPP). El siguiente código te indica cómo seleccionar el el backend de CPP específicamente, para inhabilitarlo.

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

Cómo buscar interfaces de HAL del AIDL

Las interfaces estables del AIDL del AOSP para HAL están en los mismos directorios base que Interfaces HIDL, en aidl carpetas.

  • hardware/interfaces
  • frameworks/hardware/interfaces
  • sistema/hardware/interfaces

Debes colocar interfaces de extensión en otros hardware/interfaces subdirectorios en vendor o hardware.

Interfaces de extensión

Android tiene un conjunto de interfaces oficiales del AOSP con cada versión. En Android los socios quieren agregar funcionalidad a estas interfaces, no deberían cambiar porque el tiempo de ejecución de Android incompatible con el tiempo de ejecución de AOSP para Android. En el caso de los dispositivos con GMS, evitar cambiar estas interfaces también garantizan que la imagen de GSI pueda seguir funcionando.

Las extensiones se pueden registrar de dos maneras diferentes:

  • en el entorno de ejecución, consulta Extensiones adjuntas.
  • independientes, registrados globalmente y en VINTF.

Sin embargo, se registra una extensión cuando es específica del proveedor (es decir, no forma parte de del AOSP) usan la interfaz, no hay posibilidad de fusión conflicto. Sin embargo, cuando se modifican las modificaciones descendentes en componentes ascendentes del AOSP se realizan, se pueden producir conflictos de combinación, y se recomiendan las siguientes estrategias:

  • Las incorporaciones de interfaz se pueden subir a AOSP en la próxima versión
  • adiciones de interfaz que permiten mayor flexibilidad, sin conflictos de combinación, puede ser ascendente en la próxima versión

Extensiones parcelables: ParcelableHolder

ParcelableHolder es un Parcelable que puede contener otro Parcelable. El caso de uso principal de ParcelableHolder es hacer que un elemento Parcelable sea extensible. Por ejemplo, la imagen que los implementadores de dispositivos esperan poder extender una Parcelable definido por AOSP, AospDefinedParcelable, para incluir su valor agregado atributos.

Antes sin ParcelableHolder, los implementadores de dispositivos no podían modificar una interfaz de AIDL estable definida por AOSP porque sería un error agregar más campos:

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

Como se ve en el código anterior, esta práctica no funciona porque los campos agregados por el implementador de dispositivos podrían tener un conflicto cuando el elemento revisadas en las próximas versiones de Android.

Con ParcelableHolder, el propietario de un objeto parcelable puede definir una extensión. punto en un Parcelable.

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

Luego, los implementadores de dispositivos pueden definir su propio Parcelable para su .

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

Por último, el nuevo Parcelable se puede adjuntar al Parcelable original con el 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>();

Nombres de las instancias del servidor de la HAL del AIDL

Por convención, los servicios de la HAL del AIDL tienen un nombre de instancia con este formato $package.$type/$instance Por ejemplo, una instancia de la HAL del vibrador se registrado como android.hardware.vibrator.IVibrator/default.

Escribe un servidor de la HAL del AIDL

@VintfStability Los servidores de AIDL deben declararse en el manifiesto de VINTF, para ejemplo como este:

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

De lo contrario, deberían registrar un servicio de AIDL normalmente. Cuando se ejecuta VTS , se espera que todas las HAL del AIDL declaradas estén disponibles.

Escribe un cliente de AIDL

Los clientes del AIDL deben declararse en la matriz de compatibilidad, por ejemplo, así:

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

Cómo convertir una HAL existente de HIDL a AIDL

Usa la herramienta hidl2aidl para convertir una interfaz HIDL en AIDL.

Funciones de hidl2aidl:

  • Crear archivos .aidl basados en los archivos .hal para el paquete determinado
  • Crear reglas de compilación para el paquete AIDL recién creado con todos los backends habilitado
  • Crea métodos de traducción en los backends de Java, CPP y NDK para la traducción. desde los tipos HIDL hasta los tipos de AIDL
  • Cómo crear reglas de compilación para bibliotecas de Traductor con las dependencias requeridas
  • Crea aserciones estáticas para asegurarte de que los enumeradores HIDL y AIDL tengan los mismos valores en los backends de CPP y NDK

Sigue estos pasos para convertir un paquete de archivos .hal en archivos .aidl:

  1. Compila la herramienta ubicada en system/tools/hidl/hidl2aidl.

    Compilar esta herramienta a partir de la fuente más reciente proporciona la información más completa una experiencia fluida a los desarrolladores. Puedes usar la última versión para convertir interfaces ramas de versiones anteriores.

    m hidl2aidl
    
  2. Ejecuta la herramienta con un directorio de salida seguido del paquete que se convertido.

    De manera opcional, usa el argumento -l para agregar el contenido de un archivo de licencia nuevo. a la parte superior de todos los archivos generados. Asegúrate de usar la licencia y la fecha correctas.

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

    Por ejemplo:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Lee los archivos generados y soluciona cualquier problema con la conversión.

    • conversion.log contiene problemas no controlados que se deben corregir primero.
    • Es posible que los archivos .aidl generados tengan advertencias y sugerencias necesitan alguna acción. Estos comentarios comienzan con //.
    • Aprovecha la oportunidad de realizar una limpieza y realizar mejoras en el paquete.
    • Revisa el @JavaDerive. para los atributos que podrían ser necesarios, como toString o equals
  4. Compila solo los objetivos que necesitas.

    • Inhabilita los backends que no se usarán. Opta por el backend del NDK en lugar del CPP backend, consulta cómo elegir el entorno de ejecución.
    • Quita las bibliotecas de Traductor o cualquier código generado que no se utilice.
  5. Consulta Principales diferencias de AIDL y HIDL.

    • El uso del Status integrado del AIDL y las excepciones suelen mejorar el y elimina la necesidad de otro tipo de estado específico de la interfaz.
    • Los argumentos de la interfaz del AIDL en los métodos no son @nullable de forma predeterminada, como estaban en HIDL.

SEPolicy para HAL de AIDL

Un tipo de servicio de AIDL que sea visible para el código del proveedor debe tener la atributo hal_service_type. De lo contrario, la configuración de sepolicy es la misma como cualquier otro servicio de AIDL (aunque existen atributos especiales para las HAL). Aquí Esta es una definición de ejemplo del contexto del servicio de HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Para la mayoría de los servicios definidos por la plataforma, un contexto de servicio con la ya se agregó (por ejemplo, android.hardware.foo.IFoo/default ya está marcado como hal_foo_service). Sin embargo, si un cliente del framework admite varios nombres de instancias, se deben agregar nombres de instancia adicionales en archivos service_contexts específicos del dispositivo.

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

Los atributos de la HAL deben agregarse cuando creamos un tipo nuevo de HAL. Una HAL específica pueden asociarse con varios tipos de servicios (cada uno de los cuales puede tener varias instancias, como acabamos de mencionar). Para una HAL, foo, tenemos hal_attribute(foo) Esta macro define los atributos hal_foo_client y hal_foo_server Para un dominio dado, hal_client_domain y Las macros hal_server_domain asocian un dominio con un atributo de HAL determinado. Para ejemplo, si el servidor del sistema es cliente de esta HAL, hal_client_domain(system_server, hal_foo) Un servidor de HAL también incluye hal_server_domain(my_hal_domain, hal_foo) Por lo general, para una HAL determinada , también creamos un dominio como hal_foo_default como referencia o HAL de ejemplo. Sin embargo, algunos dispositivos usan estos dominios para sus propios servidores. La distinción entre dominios para varios servidores solo es importante si varios servidores que usan la misma interfaz y necesitan un permiso diferente establecer en sus implementaciones. En todas estas macros, hal_foo no es en realidad un objeto sepolicy. En cambio, estas macros usan este token para hacer referencia a el grupo de atributos asociados con un par cliente-servidor.

Sin embargo, hasta ahora, no hemos asociado hal_foo_service ni hal_foo. (el par de atributos de hal_attribute(foo)). Se asocia un atributo de la HAL con los servicios de HAL del AIDL usando la macro hal_attribute_service (las HAL de HIDL usan la macro hal_attribute_hwservice). Por ejemplo: hal_attribute_service(hal_foo, hal_foo_service) Esto significa que Los procesos hal_foo_client pueden obtener la HAL y hal_foo_server pueden registrar la HAL. La aplicación de estas reglas de registro que realiza el administrador de contexto (servicemanager). Ten en cuenta que los nombres de los servicios no siempre se corresponden con los atributos de la HAL. Por ejemplo, podríamos ver hal_attribute_service(hal_foo, hal_foo2_service) Sin embargo, en general, ya que esto implica que los servicios siempre se usan juntos, podríamos quitar el hal_foo2_service y el uso de hal_foo_service para todo nuestro servicio diferentes. La mayoría de las HAL que establecen varios hal_attribute_service se deben a los siguientes motivos: el nombre del atributo original de HAL no es lo suficientemente general y no se puede cambiar.

Con todo esto, una HAL de ejemplo se ve de la siguiente manera:

    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 extensión conectadas

Una extensión se puede adjuntar a cualquier interfaz de Binder, ya sea una de nivel superior registrada directamente con Service Manager o una subinterfaz. Cuando obtengas una extensión, debes confirmar que su tipo sea: lo esperado. Las extensiones solo se pueden configurar desde el proceso que publica un Binder.

Las extensiones adjuntas se deben utilizar siempre que una extensión modifica la de una HAL existente. Cuando se necesita una funcionalidad completamente nueva, este mecanismo no es necesario, y se puede aplicar una interfaz de extensión registrados directamente con el administrador del servicio. Interfaces de extensión conectadas tienen más sentido cuando se adjuntan a subinterfaces, porque estas y las jerarquías pueden ser profundas o de instancias múltiples. Usa una extensión global para duplicar la jerarquía de la interfaz Binder de otro servicio requeriría una gran la contabilidad para proporcionar una funcionalidad equivalente a las extensiones adjuntas directamente.

Para configurar una extensión en Binder, usa las siguientes APIs:

  • En el backend del NDK: AIBinder_setExtension
  • En el backend de Java: android.os.Binder.setExtension
  • En el backend de la CPP: android::Binder::setExtension
  • En el backend de Rust: binder::Binder::set_extension

Para obtener una extensión en un Binder, usa las siguientes APIs:

  • En el backend del NDK: AIBinder_getExtension
  • En el backend de Java: android.os.IBinder.getExtension
  • En el backend de la CPP: android::IBinder::getExtension
  • En el backend de Rust: binder::Binder::get_extension

Para obtener más información sobre estas APIs, consulta la documentación de la getExtension en el backend correspondiente. Un ejemplo de cómo usar puedes encontrar las extensiones hardware/interfaces/tests/extension/vibrator:

Principales diferencias de AIDL y HIDL

Cuando uses las HAL del AIDL o las interfaces de la HAL del AIDL, ten en cuenta las diferencias en comparación con la escritura de HAL de HIDL.

  • La sintaxis del lenguaje de AIDL es más cercana a Java. La sintaxis del HIDL es similar a la de C++.
  • Todas las interfaces de AIDL tienen estados de error integrados. En vez de crear modelos personalizados tipos de estado, crear int de estado constantes en archivos de interfaz y usar EX_SERVICE_SPECIFIC en los backends de CPP/NDK y ServiceSpecificException en el backend de Java. Consulta Error Manipulación.
  • El AIDL no inicia automáticamente los grupos de subprocesos cuando se envían objetos Binder. Deben iniciarse de forma manual (consulta conversación administración de configuraciones).
  • El AIDL no se anula en errores de transporte no verificados (HIDL Return se anula el errores desmarcados).
  • El AIDL solo puede declarar un tipo por archivo.
  • Los argumentos del AIDL se pueden especificar como entrada, salida o entrada, además de la salida. (no hay “devoluciones de llamada síncronas”).
  • El AIDL usa un fd como el tipo primitivo en lugar de un handle.
  • HIDL usa versiones principales para cambios incompatibles y versiones secundarias para cambios compatibles. En el AIDL, se realizan los cambios retrocompatibles. El AIDL no tiene un concepto explícito de versiones principales. en cambio, esto es se incorporan en los nombres de los paquetes. Por ejemplo, el AIDL podría usar el nombre del paquete bluetooth2
  • El AIDL no hereda la prioridad en tiempo real de forma predeterminada. El setInheritRt se debe usar por vinculador para habilitar la herencia de prioridad en tiempo real.