Android 11 introduit la possibilité d'utiliser AIDL pour les HAL dans Android. Cela permet d'implémenter des parties d'Android sans HIDL. Passez aux HAL pour utiliser AIDL exclusivement lorsque cela est possible (lorsque les HAL en amont utilisent HIDL, HIDL doit être utilisé).
Les HAL qui utilisent AIDL pour communiquer entre les composants du framework, tels que ceux de system.img
, et les composants matériels, tels que ceux de vendor.img
, doivent utiliser AIDL stable. Toutefois, pour communiquer au sein d'une partition, par exemple, d'un HAL à un autre, aucune restriction n'est appliquée au mécanisme d'IPC à utiliser.
Motivation
AIDL existe depuis plus longtemps que HIDL et est utilisé dans de nombreux autres endroits, par exemple entre les composants du framework Android ou dans les applications. Maintenant qu'AIDL est compatible avec la stabilité, il est possible d'implémenter une pile entière avec un seul environnement d'exécution IPC. AIDL dispose également d'un meilleur système de gestion des versions que HIDL.
- L'utilisation d'une seule langue d'IPC signifie qu'il n'y a qu'une seule chose à apprendre, déboguer, optimiser et sécuriser.
- AIDL prend en charge la gestion des versions in situ pour les propriétaires d'une interface :
- Les propriétaires peuvent ajouter des méthodes à la fin des interfaces ou des champs aux éléments parcellables. Cela signifie qu'il est plus facile de gérer les versions du code au fil des ans, et que le coût est moins élevé d'une année à l'autre (les types peuvent être modifiés sur place et il n'est pas nécessaire d'utiliser des bibliothèques supplémentaires pour chaque version de l'interface).
- Les interfaces d'extension peuvent être associées au moment de l'exécution plutôt que dans le système de types. Il n'est donc pas nécessaire de rebaser les extensions en aval sur des versions plus récentes d'interfaces.
- Une interface AIDL existante peut être utilisée directement lorsque son propriétaire choisit de la stabiliser. Auparavant, une copie complète de l'interface devait être créée en HIDL.
Compiler avec l'environnement d'exécution AIDL
AIDL dispose de trois backends différents: Java, NDK et CPP. Pour utiliser AIDL stable, vous devez toujours utiliser la copie système de libbinder à system/lib*/libbinder.so
et communiquer sur /dev/binder
. Pour le code sur l'image du fournisseur, cela signifie que libbinder
(à partir du VNDK) ne peut pas être utilisé: cette bibliothèque possède une API C++ et des composants internes instables. À la place, le code du fournisseur natif doit utiliser le backend NDK d'AIDL, s'associer à libbinder_ndk
(qui est pris en charge par le système libbinder.so
) et s'associer aux bibliothèques NDK créées par les entrées aidl_interface
. Pour connaître les noms exacts des modules, consultez les règles d'attribution de noms aux modules.
Écrire une interface HAL AIDL
Pour qu'une interface AIDL soit utilisée entre le système et le fournisseur, elle doit être modifiée de deux façons:
- Chaque définition de type doit être annotée avec
@VintfStability
. - La déclaration
aidl_interface
doit inclurestability: "vintf",
.
Seul le propriétaire d'une interface peut effectuer ces modifications.
Lorsque vous effectuez ces modifications, l'interface doit figurer dans le fichier manifeste VINTF pour fonctionner. Testez cela (et les exigences associées, telles que la vérification que les interfaces publiées sont figées) à l'aide du test VTS vts_treble_vintf_vendor_test
. Vous pouvez utiliser une interface @VintfStability
sans ces exigences en appelant AIBinder_forceDowngradeToLocalStability
dans le backend du NDK, android::Stability::forceDowngradeToLocalStability
dans le backend C++ ou android.os.Binder#forceDowngradeToSystemStability
dans le backend Java sur un objet de liaison avant qu'il ne soit envoyé à un autre processus. La rétrogradation d'un service vers la stabilité du fournisseur n'est pas prise en charge en Java, car toutes les applications s'exécutent dans un contexte système.
De plus, pour une portabilité maximale du code et pour éviter les problèmes potentiels tels que les bibliothèques supplémentaires inutiles, désactivez le backend CPP.
Notez que l'utilisation de backends
dans l'exemple de code ci-dessous est correcte, car il existe trois backends (Java, NDK et CPP). Le code ci-dessous explique comment sélectionner spécifiquement le backend CPP pour le désactiver.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Rechercher des interfaces HAL AIDL
Les interfaces AIDL stables AOSP pour les HAL se trouvent dans les mêmes répertoires de base que les interfaces HIDL, dans les dossiers aidl
.
- hardware/interfaces: pour les interfaces généralement fournies par le matériel
- frameworks/hardware/interfaces: pour les interfaces de haut niveau fournies au matériel
- system/hardware/interfaces: pour les interfaces de bas niveau fournies au matériel
Vous devez placer les interfaces d'extension dans d'autres sous-répertoires hardware/interfaces
dans vendor
ou hardware
.
Interfaces d'extension
Android dispose d'un ensemble d'interfaces AOSP officielles à chaque version. Lorsque les partenaires Android souhaitent ajouter des fonctionnalités à ces interfaces, ils ne doivent pas les modifier directement, car cela signifierait que leur environnement d'exécution Android est incompatible avec l'environnement d'exécution Android AOSP. Pour les appareils GMS, éviter de modifier ces interfaces permet également de garantir que l'image GSI peut continuer à fonctionner.
Les extensions peuvent s'enregistrer de deux manières différentes:
- au moment de l'exécution, consultez les extensions associées.
- autonome, enregistré à l'échelle mondiale et dans VINTF.
Cependant, une extension est enregistrée. Lorsque des composants spécifiques au fournisseur (c'est-à-dire qui ne font pas partie de l'AOSP en amont) utilisent l'interface, il n'y a pas de risque de conflit de fusion. Toutefois, lorsque des modifications en aval sont apportées aux composants AOSP en amont, des conflits de fusion peuvent se produire. Les stratégies suivantes sont donc recommandées:
- Les ajouts d'interface peuvent être intégrés à AOSP dans la prochaine version
- Les ajouts d'interfaces qui offrent une plus grande flexibilité, sans conflit de fusion, peuvent être intégrés dans la prochaine version
Parcelables d'extension: ParcelableHolder
ParcelableHolder
est un Parcelable
pouvant contenir un autre Parcelable
.
Le principal cas d'utilisation de ParcelableHolder
est de rendre un Parcelable
extensible.
Par exemple, imaginez que les implémentateurs d'appareils s'attendent à pouvoir étendre un Parcelable
, AospDefinedParcelable
, défini par AOSP, pour inclure leurs fonctionnalités à valeur ajoutée.
Auparavant, sans ParcelableHolder
, les implémentateurs d'appareils ne pouvaient pas modifier une interface AIDL stable définie par AOSP, car ajouter d'autres champs entraînerait une erreur:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Comme indiqué dans le code précédent, cette pratique est cassée, car les champs ajoutés par l'implémentateur de l'appareil peuvent entrer en conflit lorsque le Parcelable est révisé dans les prochaines versions d'Android.
À l'aide de ParcelableHolder
, le propriétaire d'un parcelable peut définir un point d'extension dans un Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Les implémentateurs de l'appareil peuvent ensuite définir leur propre Parcelable
pour leur extension.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Enfin, le nouveau Parcelable
peut être associé au Parcelable
d'origine avec le champ 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>();
Noms d'instances de serveur HAL AIDL
Par convention, les services HAL AIDL ont un nom d'instance au format $package.$type/$instance
. Par exemple, une instance du HAL du vibreur est enregistrée en tant que android.hardware.vibrator.IVibrator/default
.
Écrire un serveur HAL AIDL
Les serveurs AIDL @VintfStability
doivent être déclarés dans le fichier manifeste VINTF, par exemple:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
Sinon, ils doivent enregistrer un service AIDL normalement. Lors de l'exécution des tests VTS, tous les HAL AIDL déclarés doivent être disponibles.
Écrire un client AIDL
Les clients AIDL doivent se déclarer dans la matrice de compatibilité, par exemple:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Convertir un HAL existant de HIDL en AIDL
Utilisez l'outil hidl2aidl
pour convertir une interface HIDL en AIDL.
Fonctionnalités hidl2aidl
:
- Créer des fichiers
.aidl
à partir des fichiers.hal
pour le package donné - Créez des règles de compilation pour le package AIDL nouvellement créé avec tous les backends activés.
- Créez des méthodes de traduction dans les backends Java, CPP et NDK pour traduire les types HIDL en types AIDL.
- Créer des règles de compilation pour les bibliothèques de traduction avec les dépendances requises
- Créez des assertions statiques pour vous assurer que les énumérateurs HIDL et AIDL ont les mêmes valeurs dans les backends CPP et NDK.
Pour convertir un package de fichiers .hal en fichiers .aidl, procédez comme suit:
Créez l'outil situé dans
system/tools/hidl/hidl2aidl
.Compiler cet outil à partir de la dernière source offre l'expérience la plus complète. Vous pouvez utiliser la dernière version pour convertir les interfaces sur les anciennes branches des versions précédentes.
m hidl2aidl
Exécutez l'outil avec un répertoire de sortie, suivi du package à convertir.
Vous pouvez également utiliser l'argument
-l
pour ajouter le contenu d'un nouveau fichier de licence en haut de tous les fichiers générés. Assurez-vous d'utiliser la bonne licence et la bonne date.hidl2aidl -o <output directory> -l <file with license> <package>
Exemple :
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Lisez les fichiers générés et corrigez les problèmes de conversion.
conversion.log
contient les problèmes non gérés à résoudre en premier.- Les fichiers
.aidl
générés peuvent contenir des avertissements et des suggestions nécessitant une action. Ces commentaires commencent par//
. - Profitez-en pour nettoyer et améliorer le package.
- Vérifiez l'annotation
@JavaDerive
pour les fonctionnalités qui pourraient être nécessaires, telles quetoString
ouequals
.
Ne créez que les cibles dont vous avez besoin.
- Désactivez les backends qui ne seront pas utilisés. Privilégiez le backend NDK au backend CPP. Pour en savoir plus, consultez la section Choisir l'environnement d'exécution.
- Supprimez les bibliothèques de traduction ou tout code généré qui ne sera pas utilisé.
Consultez la section Principales différences entre AIDL/HIDL.
- L'utilisation des
Status
et des exceptions intégrées d'AIDL améliore généralement l'interface et élimine le besoin d'un autre type d'état spécifique à l'interface. - Les arguments d'interface AIDL dans les méthodes ne sont pas
@nullable
par défaut, comme ils l'étaient dans HIDL.
- L'utilisation des
SEPolicy pour les HAL AIDL
Un type de service AIDL visible par le code du fournisseur doit comporter l'attribut hal_service_type
. Sinon, la configuration de la stratégie de sécurité est identique à celle de tout autre service AIDL (bien qu'il existe des attributs spéciaux pour les HAL). Voici un exemple de définition d'un contexte de service HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Pour la plupart des services définis par la plate-forme, un contexte de service avec le type approprié est déjà ajouté (par exemple, android.hardware.foo.IFoo/default
est déjà marqué comme hal_foo_service
). Toutefois, si un client de framework accepte plusieurs noms d'instance, des noms d'instance supplémentaires doivent être ajoutés dans les fichiers service_contexts
spécifiques à l'appareil.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Les attributs HAL doivent être ajoutés lorsque vous créez un type de HAL. Un attribut HAL spécifique peut être associé à plusieurs types de services (chacun pouvant avoir plusieurs instances, comme nous l'avons vu précédemment). Pour un HAL, foo
, nous avons hal_attribute(foo)
. Cette macro définit les attributs hal_foo_client
et hal_foo_server
. Pour un domaine donné, les macros hal_client_domain
et hal_server_domain
associent un domaine à un attribut HAL donné. Par exemple, le fait que le serveur système soit un client de ce HAL correspond à la stratégie hal_client_domain(system_server, hal_foo)
. Un serveur HAL inclut également hal_server_domain(my_hal_domain, hal_foo)
. En règle générale, pour un attribut HAL donné, nous créons également un domaine tel que hal_foo_default
à des fins de référence ou d'exemple de HAL. Toutefois, certains appareils utilisent ces domaines pour leurs propres serveurs.
La distinction entre les domaines pour plusieurs serveurs n'a d'importance que si plusieurs serveurs servent la même interface et ont besoin d'un ensemble d'autorisations différent dans leurs implémentations. Dans toutes ces macros, hal_foo
n'est pas réellement un objet sepolicy. À la place, ce jeton est utilisé par ces macros pour faire référence au groupe d'attributs associé à une paire client-serveur.
Cependant, jusqu'à présent, nous n'avons pas associé hal_foo_service
et hal_foo
(la paire d'attributs de hal_attribute(foo)
). Un attribut HAL est associé aux services HAL AIDL à l'aide de la macro hal_attribute_service
(les HAL HIDL utilisent la macro hal_attribute_hwservice
). Par exemple, hal_attribute_service(hal_foo, hal_foo_service)
. Cela signifie que les processus hal_foo_client
peuvent obtenir le HAL, et que les processus hal_foo_server
peuvent l'enregistrer. L'application de ces règles d'enregistrement est effectuée par le gestionnaire de contexte (servicemanager
). Notez que les noms de service ne correspondent pas toujours aux attributs HAL. Par exemple, nous pourrions voir hal_attribute_service(hal_foo, hal_foo2_service)
. Toutefois, en général, comme cela implique que les services sont toujours utilisés ensemble, nous pouvons envisager de supprimer hal_foo2_service
et d'utiliser hal_foo_service
pour tous nos contextes de service. La plupart des HAL qui définissent plusieurs hal_attribute_service
le font parce que le nom d'attribut HAL d'origine n'est pas assez général et ne peut pas être modifié.
En résumé, un exemple de HAL se présente comme suit:
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 d'extension associées
Une extension peut être associée à n'importe quelle interface de liaison, qu'il s'agisse d'une interface de niveau supérieur enregistrée directement auprès du Gestionnaire de services ou d'une sous-interface. Lorsque vous obtenez une extension, vous devez vérifier que le type de l'extension est conforme aux attentes. Les extensions ne peuvent être définies que depuis le processus qui sert un liant.
Les extensions associées doivent être utilisées chaque fois qu'une extension modifie la fonctionnalité d'un HAL existant. Lorsqu'une fonctionnalité entièrement nouvelle est nécessaire, ce mécanisme n'a pas besoin d'être utilisé, et une interface d'extension peut être enregistrée directement auprès du Gestionnaire de services. Les interfaces d'extension associées sont les plus utiles lorsqu'elles sont associées à des sous-interfaces, car ces hiérarchies peuvent être profondes ou multi-instanciées. L'utilisation d'une extension globale pour refléter la hiérarchie de l'interface de liaison d'un autre service nécessiterait une comptabilité étendue pour fournir une fonctionnalité équivalente aux extensions directement associées.
Pour définir une extension sur le liaisonneur, utilisez les API suivantes:
- Dans le backend du NDK:
AIBinder_setExtension
- Dans le backend Java:
android.os.Binder.setExtension
- Dans le backend de la page produit CSS:
android::Binder::setExtension
- Dans le backend Rust:
binder::Binder::set_extension
Pour obtenir une extension sur un liant, utilisez les API suivantes:
- Dans le backend du NDK:
AIBinder_getExtension
- Dans le backend Java:
android.os.IBinder.getExtension
- Dans le backend de la page produit CSS:
android::IBinder::getExtension
- Dans le backend Rust:
binder::Binder::get_extension
Pour en savoir plus sur ces API, consultez la documentation de la fonction getExtension
dans le backend correspondant. Vous trouverez un exemple d'utilisation des extensions dans hardware/interfaces/tests/extension/vibrator.
Principales différences entre AIDL et HIDL
Lorsque vous utilisez des HAL AIDL ou des interfaces HAL AIDL, tenez compte des différences par rapport à l'écriture de HAL HIDL.
- La syntaxe du langage AIDL est plus proche de Java. La syntaxe HIDL est semblable à celle de C++.
- Toutes les interfaces AIDL disposent d'états d'erreur intégrés. Au lieu de créer des types d'état personnalisés, créez des entiers d'état constants dans les fichiers d'interface et utilisez
EX_SERVICE_SPECIFIC
dans les backends CPP/NDK etServiceSpecificException
dans le backend Java. Consultez la section Gestion des erreurs. - AIDL ne démarre pas automatiquement les pools de threads lorsque des objets de liaison sont envoyés. Ils doivent être démarrés manuellement (voir la gestion des threads).
- AIDL ne s'arrête pas en cas d'erreurs de transport non vérifiées (HIDL
Return
s'arrête en cas d'erreurs non vérifiées). - AIDL ne peut déclarer qu'un seul type par fichier.
- Les arguments AIDL peuvent être spécifiés comme in/out/inout en plus du paramètre de sortie (il n'y a pas de "appels de procédure asynchrones").
- AIDL utilise un fd comme type primitif au lieu d'un handle.
- HIDL utilise les versions majeures pour les modifications incompatibles et les versions mineures pour les modifications compatibles. Dans AIDL, les modifications rétrocompatibles sont effectuées sur place.
AIDL ne comporte pas de concept explicite de versions majeures. Il est intégré aux noms de packages. Par exemple, AIDL peut utiliser le nom de package
bluetooth2
. - AIDL n'hérite pas de la priorité en temps réel par défaut. La fonction
setInheritRt
doit être utilisée par liaison pour activer l'héritage de priorité en temps réel.
Tests de la suite de test de fournisseur (VTS) pour les HAL
Android s'appuie sur la suite de test de fournisseur (VTS) pour vérifier les implémentations HAL attendues. VTS permet de s'assurer qu'Android est rétrocompatible avec les anciennes implémentations des fournisseurs. Les implémentations qui échouent au VTS présentent des problèmes de compatibilité connus qui pourraient les empêcher de fonctionner avec les futures versions du système d'exploitation.
Le VTS pour les HAL se compose de deux grandes parties.
1. Vérifiez que les HAL de l'appareil sont connus et attendus par Android.
Cet ensemble de tests se trouve dans test/vts-testcase/hal/treble/vintf. Il est chargé de vérifier les éléments suivants:
- Chaque interface
@VintfStability
déclarée dans un fichier manifeste VINTF est figée à une version publiée connue. Cela garantit que les deux côtés de l'interface sont d'accord sur la définition exacte de cette version de l'interface. Cela est nécessaire pour le fonctionnement de base. - Tous les HAL déclarés dans un fichier manifeste VINTF sont disponibles sur cet appareil. Tout client disposant d'autorisations suffisantes pour utiliser un service HAL déclaré doit pouvoir obtenir et utiliser ces services à tout moment.
- Tous les HAL déclarés dans un fichier manifeste VINTF diffusent la version de l'interface qu'ils déclarent dans le fichier manifeste.
- Aucun HAL obsolète n'est diffusé sur un appareil. Android abandonne la prise en charge des versions antérieures des interfaces HAL, comme décrit dans le cycle de vie de FCM.
- Les HAL requis sont présents sur l'appareil. Certains HAL sont nécessaires pour que Android fonctionne correctement.
2. Vérifier le comportement attendu de chaque HAL
Chaque interface HAL dispose de ses propres tests VTS pour vérifier le comportement attendu de ses clients. Les cas de test s'exécutent sur chaque instance d'une interface HAL déclarée et appliquent un comportement spécifique en fonction de la version de l'interface implémentée.
Ces tests tentent de couvrir tous les aspects de l'implémentation HAL sur lesquels le framework Android s'appuie ou sur lesquels il pourra s'appuyer à l'avenir.
Ces tests incluent la vérification de la prise en charge des fonctionnalités, de la gestion des erreurs et de tout autre comportement qu'un client peut attendre du service.
Jalons VTS pour le développement de HAL
Les tests VTS doivent être mis à jour lors de la création ou de la modification des interfaces HAL d'Android.
Les tests VTS doivent être terminés et prêts à valider les implémentations du fournisseur avant d'être figés pour les versions de l'API du fournisseur Android. Elles doivent être prêtes avant que les interfaces ne soient figées afin que les développeurs puissent créer leurs implémentations, les valider et fournir des commentaires aux développeurs de l'interface HAL.
VTS sur Cuttlefish
Lorsque le matériel n'est pas disponible, Android utilise Cuttlefish comme véhicule de développement pour les interfaces HAL. Cela permet d'effectuer des tests d'intégration et de VTS évolutifs sur Android.
hal_implementation_test
vérifie que Cuttlefish dispose d'implémentations des dernières versions d'interface HAL pour s'assurer qu'Android est prêt à gérer les nouvelles interfaces et que les tests VTS sont prêts à tester les nouvelles implémentations du fournisseur dès que de nouveaux matériels et appareils seront disponibles.