Android 11 presenta la capacidad de usar AIDL para HAL en Android. Esto permite implementar partes de Android sin HIDL. Realiza la transición de los HAL para usar AIDL de forma exclusiva siempre que sea posible (cuando los HAL upstream usan HIDL, se debe usar HIDL).
Los 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, de un HAL a otro, no hay restricciones en el mecanismo de IPC que se debe usar.
Motivación
AIDL existe desde hace más tiempo que HIDL y se usa en muchos otros lugares, como entre componentes del framework de Android o en apps. Ahora que AIDL tiene compatibilidad con la estabilidad, es posible implementar una pila completa con un solo entorno de ejecución de IPC. AIDL también tiene un mejor sistema de control de versiones que HIDL.
- Usar un solo lenguaje de IPC significa tener solo un elemento para aprender, depurar, optimizar y proteger.
- AIDL admite el control de versiones in situ para los propietarios de una interfaz:
- Los propietarios pueden agregar métodos al final de las interfaces o campos a los elementos parcelables. Esto significa que es más fácil crear versiones de código a lo largo de los años y que el costo anual es menor (los tipos se pueden modificar en su lugar y no se necesitan bibliotecas adicionales para cada versión de la interfaz).
- Las interfaces de extensión se pueden adjuntar en el tiempo de ejecución en lugar de en el sistema de tipos, por lo que no es necesario volver a basar las extensiones descendentes en versiones más recientes de las interfaces.
- Se puede usar directamente una interfaz de AIDL existente cuando su propietario decide estabilizarla. Antes, se debía crear una copia completa de la interfaz en HIDL.
Compila con el entorno de ejecución de AIDL
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 comunicarte en /dev/binder
. En el caso del código de la imagen del proveedor, esto significa que no se puede usar libbinder
(del VNDK): esta biblioteca tiene una API de C++ inestable y elementos internos inestables. En su lugar, el código nativo del proveedor debe usar el backend del NDK de AIDL, vincularse a libbinder_ndk
(que está respaldado por el sistema libbinder.so
) y vincularse a las bibliotecas del NDK creadas por las entradas aidl_interface
. Para conocer los nombres exactos de los módulos, consulta las reglas de nombres de los módulos.
Cómo escribir una interfaz de HAL de AIDL
Para que se use una interfaz AIDL entre el sistema y el proveedor, la interfaz necesita dos cambios:
- Cada definición de tipo debe estar anulada con
@VintfStability
. - La declaración
aidl_interface
debe incluirstability: "vintf",
.
Solo el propietario de una interfaz puede realizar estos cambios.
Cuando realices estos cambios, la interfaz debe estar en el manifiesto de VINTF para que funcione. Prueba esto (y los requisitos relacionados, como verificar que las interfaces publicadas estén inmovilizadas) con la prueba de VTS vts_treble_vintf_vendor_test
. Para usar una interfaz @VintfStability
sin estos requisitos, llama a AIBinder_forceDowngradeToLocalStability
en el backend de NDK, android::Stability::forceDowngradeToLocalStability
en el backend de C++, o android.os.Binder#forceDowngradeToSystemStability
en el backend de Java en un objeto Binder antes de que se envíe a otro proceso. No se admite la baja de versión de un servicio a la estabilidad del proveedor en Java porque todas las apps se ejecutan en un contexto del sistema.
Además, para obtener la máxima portabilidad de código y evitar posibles problemas, 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 hay tres backends (Java, NDK y CPP). En el siguiente código, se indica cómo seleccionar el backend de CPP específicamente para inhabilitarlo.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Cómo encontrar interfaces de HAL de AIDL
Las interfaces de AIDL estables de AOSP para HAL se encuentran en los mismos directorios base que las interfaces de HIDL, en las carpetas aidl
.
- hardware/interfaces: Para interfaces que suele proporcionar el hardware
- frameworks/hardware/interfaces: Para interfaces de alto nivel proporcionadas al hardware
- system/hardware/interfaces: Para interfaces de bajo nivel proporcionadas al hardware
Debes colocar las interfaces de extensión en otros subdirectorios hardware/interfaces
en vendor
o hardware
.
Interfaces de extensión
Android tiene un conjunto de interfaces oficiales de AOSP con cada versión. Cuando los socios de Android desean agregar funcionalidad a estas interfaces, no deben cambiarlas directamente, ya que esto significaría que su entorno de ejecución de Android no es compatible con el entorno de ejecución de Android de AOSP. En el caso de los dispositivos GMS, evitar cambiar estas interfaces también garantiza que la imagen de GSI pueda seguir funcionando.
Las extensiones se pueden registrar de dos maneras diferentes:
- en el tiempo de ejecución, consulta Extensiones adjuntas.
- independiente, registrado a nivel global y en VINTF.
Sin embargo, independientemente de cómo se registre una extensión, cuando los componentes específicos del proveedor (es decir, que no forman parte del AOSP upstream) usan la interfaz, no hay posibilidad de conflicto de combinación. Sin embargo, cuando se realizan modificaciones descendentes en los componentes de AOSP ascendentes, pueden producirse conflictos de combinación, y se recomiendan las siguientes estrategias:
- Las incorporaciones de la interfaz se pueden transferir a AOSP en la próxima versión.
- Las incorporaciones de interfaz que permiten una mayor flexibilidad, sin conflictos de combinación, se pueden transferir a la versión principal en la próxima versión.
Objetos parcelables de la extensión: ParcelableHolder
ParcelableHolder
es un Parcelable
que puede contener otro Parcelable
.
El caso de uso principal de ParcelableHolder
es hacer que un Parcelable
sea extensible.
Por ejemplo, imagina que los implementadores de dispositivos esperan poder extender un Parcelable
, AospDefinedParcelable
, definido por AOSP, para incluir sus funciones de valor agregado.
Anteriormente, sin ParcelableHolder
, los implementadores de dispositivos no podían modificar una interfaz 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 se interrumpe porque los campos que agrega el implementador del dispositivo pueden tener un conflicto cuando se revisa el objeto Parcelable en las próximas versiones de Android.
Con ParcelableHolder
, el propietario de un elemento parcelable puede definir un punto de extensión en un Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Luego, los implementadores de dispositivos pueden definir su propio Parcelable
para su extensión.
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 instancias de servidor de HAL de AIDL
Por convención, los servicios de HAL de AIDL tienen un nombre de instancia del formato $package.$type/$instance
. Por ejemplo, una instancia del HAL del vibrador se registra como android.hardware.vibrator.IVibrator/default
.
Cómo escribir un servidor de HAL de AIDL
Los servidores AIDL de @VintfStability
se deben declarar en el manifiesto de VINTF, por ejemplo, de la siguiente manera:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
De lo contrario, deben registrar un servicio AIDL de forma normal. Cuando se ejecutan pruebas de VTS, se espera que todos los HAL de AIDL declarados estén disponibles.
Cómo escribir un cliente de AIDL
Los clientes de AIDL deben declararse en la matriz de compatibilidad, por ejemplo, de la siguiente manera:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Convierte un HAL existente de HIDL a AIDL
Usa la herramienta hidl2aidl
para convertir una interfaz HIDL en AIDL.
Funciones de hidl2aidl
:
- Crea archivos
.aidl
en función de los archivos.hal
del paquete determinado. - Crea reglas de compilación para el paquete AIDL recién creado con todos los backends habilitados.
- Crea métodos de traducción en los backends de Java, CPP y NDK para traducir de los tipos de HIDL a los de AIDL.
- Crea reglas de compilación para bibliotecas de traducción con dependencias requeridas
- Crea aserciones estáticas para asegurarte de que los enumeradores de 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:
Compila la herramienta que se encuentra en
system/tools/hidl/hidl2aidl
.La compilación de esta herramienta a partir de la fuente más reciente proporciona la experiencia más completa. Puedes usar la versión más reciente para convertir interfaces en ramas más antiguas de versiones anteriores.
m hidl2aidl
Ejecuta la herramienta con un directorio de salida seguido del paquete que se convertirá.
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
Lee los archivos generados y corrige cualquier problema con la conversión.
conversion.log
contiene los problemas no controlados que se deben corregir primero.- Es posible que los archivos
.aidl
generados tengan advertencias y sugerencias que requieran acciones. Estos comentarios comienzan con//
. - Aprovecha la oportunidad para limpiar y mejorar el paquete.
- Revisa la anotación
@JavaDerive
para ver las funciones que podrían ser necesarias, comotoString
oequals
.
Compila solo los destinos que necesites.
- Inhabilita los backends que no se usarán. Prefiere el backend de NDK en lugar del backend de CPP. Consulta Cómo elegir el entorno de ejecución.
- Quita las bibliotecas de traducción o cualquier código generado que no se usará.
Consulta Diferencias principales entre AIDL y HIDL.
- El uso de
Status
y excepciones integradas de AIDL suele mejorar la interfaz y quitar la necesidad de otro tipo de estado específico de la interfaz. - Los argumentos de interfaz de AIDL en los métodos no son
@nullable
de forma predeterminada, como lo eran en HIDL.
- El uso de
SEPolicy para HAL de AIDL
Un tipo de servicio AIDL que sea visible para el código del proveedor debe tener el atributo hal_service_type
. De lo contrario, la configuración de la política de SE es la misma que la de cualquier otro servicio de AIDL (aunque hay atributos especiales para los HAL). Este es un ejemplo de definición de un contexto de servicio de HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Para la mayoría de los servicios definidos por la plataforma, ya se agregó un contexto de servicio con el tipo correcto (por ejemplo, android.hardware.foo.IFoo/default
ya estaría marcado como hal_foo_service
). Sin embargo, si un cliente de framework admite varios nombres de instancias, se deben agregar nombres de instancias adicionales en los archivos service_contexts
específicos del dispositivo.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Los atributos HAL se deben agregar cuando creamos un nuevo tipo de HAL. Un atributo HAL específico puede asociarse con varios tipos de servicios (cada uno de los cuales puede tener varias instancias, como acabamos de mencionar). Para un HAL, foo
, tenemos hal_attribute(foo)
. Esta macro define los atributos hal_foo_client
y hal_foo_server
. Para un dominio determinado, las macros hal_client_domain
y hal_server_domain
asocian un dominio con un atributo HAL determinado. Por ejemplo, el servidor del sistema como cliente de este HAL corresponde a la política hal_client_domain(system_server, hal_foo)
. Un servidor HAL también incluye hal_server_domain(my_hal_domain, hal_foo)
. Por lo general, para un atributo HAL determinado, también creamos un dominio como hal_foo_default
para referencia o HALs de ejemplo. Sin embargo, algunos dispositivos usan estos dominios para sus propios servidores.
Distinguir entre dominios para varios servidores solo es importante si tenemos varios servidores que entregan la misma interfaz y necesitan un conjunto de permisos diferente en sus implementaciones. En todas estas macros, hal_foo
no es un objeto de sepolicy. En su lugar, estas macros usan este token para hacer referencia al grupo de atributos asociados con un par de servidores de clientes.
Sin embargo, hasta ahora, no asociamos hal_foo_service
y hal_foo
(el par de atributos de hal_attribute(foo)
). Un atributo HAL se asocia con los servicios de HAL de AIDL mediante la macro hal_attribute_service
(los HAL de HIDL usan la macro hal_attribute_hwservice
). Por ejemplo, hal_attribute_service(hal_foo, hal_foo_service)
. Esto significa que los procesos de hal_foo_client
pueden obtener el HAL, y los procesos de hal_foo_server
pueden registrarlo. El administrador de contexto (servicemanager
) aplica estas reglas de registro. Ten en cuenta que los nombres de los servicios no siempre corresponden a atributos de HAL. Por ejemplo, es posible que veamos hal_attribute_service(hal_foo, hal_foo2_service)
. Sin embargo, en general, como esto implica que los servicios siempre se usan juntos, podríamos considerar quitar hal_foo2_service
y usar hal_foo_service
para todos nuestros contextos de servicio. La mayoría de los HAL que establecen varios hal_attribute_service
se deben a que el nombre del atributo HAL original no es lo suficientemente general y no se puede cambiar.
Si juntamos todo esto, un ejemplo de HAL 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 adjuntas
Se puede adjuntar una extensión a cualquier interfaz de Binder, ya sea una interfaz de nivel superior registrada directamente con el administrador de servicios o una subinterfaz. Cuando obtienes una extensión, debes confirmar que el tipo sea el esperado. Las extensiones solo se pueden configurar desde el proceso que entrega un Binder.
Las extensiones adjuntas deben usarse cada vez que una extensión modifica la funcionalidad de un HAL existente. Cuando se necesita una funcionalidad completamente nueva, no es necesario usar este mecanismo, y se puede registrar una interfaz de extensión directamente con el administrador de servicios. Las interfaces de extensión adjuntas tienen más sentido cuando se adjuntan a subinterfaces, ya que estas jerarquías pueden ser profundas o multiinstancias. El uso de una extensión global para duplicar la jerarquía de la interfaz de Binder de otro servicio requeriría una contabilidad extensa para proporcionar una funcionalidad equivalente a las extensiones conectadas 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
Puedes encontrar más información sobre estas APIs en la documentación de la función getExtension
en el backend correspondiente. Puedes encontrar un ejemplo de cómo usar extensiones en hardware/interfaces/tests/extension/vibrator.
Diferencias principales entre AIDL y HIDL
Cuando uses HAL de AIDL o interfaces de HAL de AIDL, ten en cuenta las diferencias en comparación con la escritura de HAL de HIDL.
- La sintaxis del lenguaje AIDL es más cercana a Java. La sintaxis de HIDL es similar a la de C++.
- Todas las interfaces de AIDL tienen estados de error integrados. En lugar de crear tipos de estado personalizados, crea números enteros de estado constantes en los archivos de interfaz y usa
EX_SERVICE_SPECIFIC
en los backends de CPP/NDK yServiceSpecificException
en el backend de Java. Consulta Control de errores. - AIDL no inicia automáticamente los grupos de subprocesos cuando se envían objetos de Binder. Deben iniciarse de forma manual (consulta la administración de subprocesos).
- AIDL no se cancela en errores de transporte no verificados (HIDL
Return
se cancela en errores no verificados). - AIDL solo puede declarar un tipo por archivo.
- Los argumentos de AIDL se pueden especificar como entrada, salida o entrada y salida, además del parámetro de salida (no hay "callbacks síncronas").
- AIDL usa un fd como el tipo primitivo en lugar del control.
- HIDL usa versiones principales para cambios incompatibles y versiones secundarias para cambios compatibles. En AIDL, los cambios retrocompatibles se realizan en su lugar.
AIDL no tiene un concepto explícito de versiones principales; en su lugar, se incorpora a los nombres de los paquetes. Por ejemplo, AIDL podría usar el nombre del paquete
bluetooth2
. - AIDL no hereda la prioridad en tiempo real de forma predeterminada. La función
setInheritRt
se debe usar por vinculador para habilitar la herencia de prioridad en tiempo real.
Pruebas del paquete de pruebas de proveedores (VTS) para HAL
Android se basa en el Conjunto de pruebas de proveedores (VTS) para verificar las implementaciones de HAL esperadas. Los VTS ayudan a garantizar que Android sea retrocompatible con implementaciones de proveedores anteriores. Las implementaciones que no pasan las pruebas de VTS tienen problemas de compatibilidad conocidos que podrían impedir que funcionen con versiones futuras del SO.
Hay dos partes principales de VTS para HAL.
1. Verifica que Android conozca y espere los HAL del dispositivo.
Puedes encontrar este conjunto de pruebas en test/vts-testcase/hal/treble/vintf. Son responsables de verificar lo siguiente:
- Cada interfaz
@VintfStability
que se declara en un manifiesto de VINTF se inmoviliza en una versión publicada conocida. Esto garantiza que ambos lados de la interfaz estén de acuerdo con la definición exacta de esa versión de la interfaz. Esto es necesario para la operación básica. - Todos los HAL que se declaran en un manifiesto de VINTF están disponibles en ese dispositivo. Cualquier cliente con permisos suficientes para usar un servicio de HAL declarado debe poder obtener y usar esos servicios en cualquier momento.
- Todos los HAL que se declaran en un manifiesto de VINTF entregan la versión de la interfaz que se declara en el manifiesto.
- No se entregan HAL obsoletos en un dispositivo. Android deja de admitir versiones anteriores de las interfaces de HAL, como se describe en el Ciclo de vida de FCM.
- Los HAL necesarios están presentes en el dispositivo. Algunos HAL son necesarios para que Android funcione correctamente.
2. Verifica el comportamiento esperado de cada HAL
Cada interfaz de HAL tiene sus propias pruebas de VTS para verificar el comportamiento esperado de sus clientes. Los casos de prueba se ejecutan en cada instancia de una interfaz HAL declarada y aplican un comportamiento específico según la versión de la interfaz que se implementa.
Estas pruebas intentan abarcar todos los aspectos de la implementación de HAL en los que se basa el framework de Android o en los que podría basarse en el futuro.
Estas pruebas incluyen verificar la compatibilidad con las funciones, la administración de errores y cualquier otro comportamiento que un cliente pueda esperar del servicio.
Hitos de VTS para el desarrollo de HAL
Se espera que las pruebas de VTS se mantengan actualizadas cuando se creen o modifiquen las interfaces de HAL de Android.
Las pruebas de VTS deben estar terminadas y listas para verificar las implementaciones del proveedor antes de que se congelen para las versiones de la API de proveedores de Android. Deben estar listos antes de que se congelen las interfaces para que los desarrolladores puedan crear sus implementaciones, verificarlas y proporcionar comentarios a los desarrolladores de la interfaz HAL.
VTS en Cuttlefish
Cuando el hardware no está disponible, Android usa Cuttlefish como vehículo de desarrollo para las interfaces de HAL. Esto permite realizar pruebas de integración y VTS escalables de Android.
hal_implementation_test
prueba que Cuttlefish tenga implementaciones de las versiones más recientes de la interfaz de HAL para asegurarse de que Android esté listo para controlar las interfaces nuevas y que las pruebas de VTS estén listas para probar las nuevas implementaciones de proveedores en cuanto haya hardware y dispositivos nuevos disponibles.