Android 11 presenta la capacidad de usar AIDL para HAL en Android, lo que 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 sobre 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. Estas son algunas de las ventajas de AIDL:
- 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 del 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 elige 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, usa siempre la copia del sistema de libbinder
en system/lib*/libbinder.so
y habla en /dev/binder
. En el caso del código de la imagen vendor
, 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 libbinder.so
del sistema) y vincularse a las bibliotecas del NDK creadas por las entradas de aidl_interface
. Para conocer los nombres exactos de los módulos, consulta Reglas de nombres de 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 anotonada 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.
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 código, se muestra cómo inhabilitar un backend de CPP:
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Busca interfaces de HAL de AIDL
Las interfaces AIDL estables de AOSP para HAL se encuentran dentro de las carpetas aidl
en los mismos directorios de base que las interfaces HIDL:
hardware/interfaces
es para interfaces que suele proporcionar el hardware.frameworks/hardware/interfaces
es para interfaces de alto nivel que se proporcionan al hardware.system/hardware/interfaces
es para interfaces de bajo nivel que se proporcionan al hardware.
Coloca 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 capacidades a estas interfaces, no deben cambiarlas directamente, ya que esto hace que su entorno de ejecución de Android sea incompatible con el entorno de ejecución de Android de AOSP. Evita cambiar estas interfaces para que la imagen de GSI pueda seguir funcionando.
Las extensiones se pueden registrar de dos maneras diferentes:
- En el tiempo de ejecución (consulta Interfaces de extensión adjuntas)
- De forma 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 es posible que se produzcan conflictos de combinación. Sin embargo, cuando se realizan modificaciones downstream en los componentes del AOSP upstream, pueden producirse conflictos de combinación, y se recomiendan las siguientes estrategias:
- Envía las incorporaciones de interfaz a AOSP en la próxima versión.
- Se agregaron interfaces upstream que permiten mayor flexibilidad (sin conflictos de combinación) en la próxima versión.
Objetos parcelables de la extensión: ParcelableHolder
ParcelableHolder
es una instancia de la interfaz Parcelable
que puede contener otra instancia de Parcelable
.
El caso de uso principal de ParcelableHolder
es hacer que Parcelable
sea extensible.
Por ejemplo, la imagen que los implementadores de dispositivos esperan poder extender un Parcelable
, AospDefinedParcelable
, definido por AOSP para incluir sus funciones de valor agregado.
Usa la interfaz ParcelableHolder
para extender Parcelable
con tus funciones de valor agregado. La interfaz ParcelableHolder
contiene una instancia de Parcelable
. Si intentas agregar campos a Parcelable
directamente, se producirá un error:
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 revise Parcelable
en las próximas versiones de Android.
Con ParcelableHolder
, el propietario de un elemento parcelable puede definir un punto de extensión en una instancia de Parcelable
:
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Luego, los implementadores de dispositivos pueden definir su propia instancia de Parcelable
para su extensión:
parcelable OemDefinedParcelable {
String x;
int[] y;
}
La nueva instancia de Parcelable
se puede adjuntar a la 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:
<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:
<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 (
.aidl
) basados en los archivos HAL (.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 HIDL a los tipos 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 solucionar primero.- Es posible que los archivos AIDL generados tengan advertencias y sugerencias que requieran acciones. Estos comentarios comienzan con
//
. - Limpia y realiza mejoras en el paquete.
- Revisa la anotación
@JavaDerive
para ver las funciones que podrían ser necesarias, comotoString
oequals
.
Compila solo los destinos que necesitas:
- Inhabilita los backends que no se usarán. Prefiere el backend de NDK en lugar del backend de CPP. Consulta Cómo compilar para el entorno de ejecución de AIDL.
- Quita las bibliotecas de traducción o cualquier código generado que no se use.
Consulta las principales diferencias 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). A continuación, se muestra una definición de ejemplo 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 está 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
Cuando creas un tipo nuevo de HAL, debes agregar atributos HAL. Un atributo HAL específico puede asociarse con varios tipos de servicios (cada uno de los cuales puede tener varias instancias, como se acaba de mencionar). Para un HAL, foo
, hay 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 se crea un dominio como hal_foo_default
para HALs de referencia o de ejemplo. Sin embargo, algunos dispositivos usan estos dominios para sus propios servidores. Distinguir entre dominios para varios servidores solo es importante si hay 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 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, hal_foo_service
y hal_foo
(el par de atributos de hal_attribute(foo)
) no están asociados. Un atributo HAL se asocia con los servicios de HAL de AIDL a través de 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.
Es posible que los nombres de los servicios no siempre correspondan a atributos HAL, por ejemplo, hal_attribute_service(hal_foo, hal_foo2_service)
. En general, como esto implica que los servicios siempre se usan juntos, puedes quitar hal_foo2_service
y usar hal_foo_service
para todos los contextos de servicio. Cuando los HAL establecen varias instancias de hal_attribute_service
, es porque 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 obtengas una extensión, debes confirmar que el tipo sea el esperado. Puedes configurar extensiones solo desde el proceso que entrega un Binder.
Usa extensiones adjuntas cada vez que una extensión modifique la funcionalidad de un HAL existente. Cuando se necesita una función completamente nueva, este mecanismo no es necesario, y puedes 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 requiere una contabilidad exhaustiva para proporcionar capacidades equivalentes a las extensiones conectadas directamente.
Para establecer una extensión en un Binder, usa las siguientes APIs:
- Backend de NDK:
AIBinder_setExtension
- Backend de Java:
android.os.Binder.setExtension
- Backend de CPP:
android::Binder::setExtension
- Backend de Rust:
binder::Binder::set_extension
Para obtener una extensión en un Binder, usa las siguientes APIs:
- Backend de NDK:
AIBinder_getExtension
- Backend de Java:
android.os.IBinder.getExtension
- Backend de CPP:
android::IBinder::getExtension
- 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. Un ejemplo de cómo usar extensiones se encuentra 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 y NDK, yServiceSpecificException
en el backend de Java. Consulta Manejo de errores. - AIDL no inicia automáticamente los grupos de subprocesos cuando se envían objetos de Binder. Debes iniciarlos de forma manual (consulta Administración de subprocesos).
- AIDL no aborta en errores de transporte no verificados (HIDL
Return
aborta en errores no verificados). - AIDL solo puede declarar un tipo por archivo.
- Los argumentos de AIDL se pueden especificar como
in
,out
oinout
, además del parámetro de salida (no hay devoluciones de llamada síncronas). - AIDL usa
fd
como el tipo primitivo en lugar dehandle
. - 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 Binder 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
Este conjunto de pruebas se encuentra en test/vts-testcase/hal/treble/vintf
.
Estas pruebas 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 publican la versión de la interfaz que declaran 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 de los proveedores 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.