Interfaces et packages

HIDL est basé sur des interfaces, un type abstrait utilisé dans les langages orientés objet pour définir des comportements. Chaque interface fait partie d'un package.

Packages

Les noms de packages peuvent comporter des sous-niveaux, comme package.subpackage. Le répertoire racine des packages HIDL publiés est hardware/interfaces ou vendor/vendorName (par exemple, vendor/google pour les appareils Pixel). Le nom du package forme un ou plusieurs sous-répertoires sous le répertoire racine. Tous les fichiers définissant un package se trouvent dans le même répertoire. Par exemple, package android.hardware.example.extension.light@2.0 peut se trouver sous hardware/interfaces/example/extension/light/2.0.

Le tableau suivant répertorie les préfixes et les emplacements des packages:

Préfixe de package Position Types d'interfaces
android.hardware.* hardware/interfaces/* HAL
android.frameworks.* frameworks/hardware/interfaces/* frameworks/ related
android.system.* system/hardware/interfaces/* system/
android.hidl.* system/libhidl/transport/* core

Le répertoire du package contient des fichiers avec l'extension .hal. Chaque fichier doit contenir une instruction package désignant le package et la version auxquels le fichier appartient. Le fichier types.hal, le cas échéant, ne définit pas d'interface, mais définit les types de données accessibles à chaque interface du package.

Définition de l'interface

À l'exception de types.hal, chaque autre fichier .hal définit une interface. Une interface est généralement définie comme suit:

interface IBar extends IFoo { // IFoo is another interface
    // embedded types
    struct MyStruct {/*...*/};

    // interface methods
    create(int32_t id) generates (MyStruct s);
    close();
};

Une interface sans déclaration extends explicite étend implicitement android.hidl.base@1.0::IBase (comme java.lang.Object en Java). L'interface IBase, importée implicitement, déclare plusieurs méthodes réservées qui ne doivent pas être redéfinies dans les interfaces définies par l'utilisateur ni utilisées autrement. Voici quelques-unes de ces méthodes:

  • ping
  • interfaceChain
  • interfaceDescriptor
  • notifySyspropsChanged
  • linkToDeath
  • unlinkToDeath
  • setHALInstrumentation
  • getDebugInfo
  • debug
  • getHashChain

Processus d'importation

L'instruction import est un mécanisme HIDL permettant d'accéder aux interfaces et aux types de packages dans un autre package. Une instruction import concerne deux entités:

  • L'entité importatrice, qui peut être un package ou une interface
  • L'entité importée, qui peut être un package ou une interface

L'entité importatrice est déterminée par l'emplacement de l'instruction import. Lorsque l'instruction se trouve dans le types.hal d'un package, l'élément importé est visible par l'ensemble du package. Il s'agit d'une importation au niveau du package. Lorsque l'instruction se trouve dans un fichier d'interface, l'entité importatrice est l'interface elle-même. Il s'agit d'une importation au niveau de l'interface.

L'entité importée est déterminée par la valeur après le mot clé import. La valeur n'a pas besoin d'être un nom complet. Si un composant est omis, il est automatiquement renseigné avec les informations du package actuel. Pour les valeurs entièrement qualifiées, les cas d'importation suivants sont acceptés:

  • Importations de l'intégralité du package. Si la valeur correspond à un nom de package et à une version (syntaxe décrite ci-dessous), l'ensemble du package est importé dans l'entité qui importe.
  • Importations partielles Si la valeur est :
    • Une interface, le types.hal du package et cette interface sont importés dans l'entité importatrice.
    • Si un UDT est défini dans types.hal, seul ce UDT est importé dans l'entité importatrice (les autres types de types.hal ne sont pas importés).
  • Importations de types uniquement. Si la valeur utilise la syntaxe d'une importation partielle décrite ci-dessus, mais avec le mot clé types au lieu d'un nom d'interface, seules les UDT de types.hal du package désigné sont importées.

L'entité qui importe a accès à une combinaison des éléments suivants:

  • Les UDT courantes du package importé définies dans types.hal ;
  • Interfaces du package importé (pour une importation de l'intégralité du package) ou interface spécifiée (pour une importation partielle) afin de les appeler, de leur transmettre des poignées et/ou d'en hériter.

L'instruction d'importation utilise la syntaxe de nom de type complet pour fournir le nom et la version du package ou de l'interface importés:

import android.hardware.nfc@1.0;            // import a whole package
import android.hardware.example@1.0::IQuux; // import an interface and types.hal
import android.hardware.example@1.0::types; // import just types.hal

Héritage d'interface

Une interface peut être une extension d'une interface précédemment définie. Les extensions peuvent être de trois types:

  • L'interface peut ajouter des fonctionnalités à une autre, en intégrant son API sans la modifier.
  • Un package peut ajouter des fonctionnalités à un autre, en intégrant son API sans la modifier.
  • L'interface peut importer des types à partir d'un package ou d'une interface spécifique.

Une interface ne peut étendre qu'une seule autre interface (pas d'héritage multiple). Chaque interface d'un package avec un numéro de version mineure différent de zéro doit étendre une interface de la version précédente du package. Par exemple, si une interface IBar de la version 4.0 du package derivative est basée sur (étend) une interface IFoo de la version 1.2 du package original et qu'une version 1.3 du package original est créée, la version 4.1 de IBar ne peut pas étendre la version 1.3 de IFoo. Au lieu de cela, la version 4.1 de IBar doit étendre la version 4.0 de IBar, qui est associée à la version 1.2 de IFoo. La version 5.0 de IBar peut étendre la version 1.3 de IFoo, si vous le souhaitez.

Les extensions d'interface n'impliquent pas de dépendance de bibliothèque ni d'inclusion multi-HAL dans le code généré. Elles importent simplement la structure de données et les définitions de méthode au niveau HIDL. Chaque méthode d'un HAL doit être implémentée dans ce HAL.

Extensions de fournisseurs

Dans certains cas, les extensions du fournisseur sont implémentées en tant que sous-classe de l'objet de base qui représente l'interface de base qu'elles étendent. Le même objet est enregistré sous le nom et la version de la HAL de base, ainsi que sous le nom et la version de la HAL de l'extension (fournisseur).

Gestion des versions

Les packages sont versionnés, et les interfaces disposent de la version de leur package. Les versions sont exprimées en deux entiers, major.minor.

  • Les versions majeurs ne sont pas rétrocompatibles. L'incrémentation du numéro de version majeure réinitialise le numéro de version mineure sur 0.
  • Les versions mineures sont rétrocompatibles. L'incrémentation du numéro mineur indique que la nouvelle version est entièrement rétrocompatible avec la version précédente. Vous pouvez ajouter de nouvelles structures de données et de méthodes, mais vous ne pouvez pas modifier les structures de données ni les signatures de méthode existantes.

Plusieurs versions majeures ou mineures d'un HAL peuvent être présentes simultanément sur un appareil. Toutefois, une version mineure est préférable à une version majeure, car le code client qui fonctionne avec une interface de version mineure précédente fonctionne également avec les versions mineures ultérieures de cette même interface. Pour en savoir plus sur la gestion des versions et les extensions de fournisseurs, consultez la section Gestion des versions HIDL.

Résumé de la mise en page de l'interface

Cette section explique comment gérer un package d'interface HIDL (comme hardware/interfaces) et consolide les informations présentées dans la section HIDL. Avant de lire cet article, assurez-vous de connaître les concepts de gestion des versions HIDL et de hachage avec hidl-gen, les détails de l'utilisation de HIDL en général et les définitions suivantes:

Terme Définition
interface binaire d'application (ABI) Interface de programmation d'application, ainsi que les liaisons binaires requises.
nom complet (fqName) Nom permettant de distinguer un type hidl. Exemple : android.hardware.foo@1.0::IFoo.
colis Package contenant une interface et des types HIDL. Exemple : android.hardware.foo@1.0.
racine du package Package racine contenant les interfaces HIDL. Exemple: l'interface HIDL android.hardware se trouve dans la racine du package android.hardware.foo@1.0.
chemin d'accès racine du package Emplacement dans l'arborescence source Android auquel une racine de package est mappée.

Pour en savoir plus, consultez la terminologie HIDL.

Chaque fichier peut être trouvé à partir du mappage racine du package et de son nom complet.

Les racines de package sont spécifiées dans hidl-gen en tant qu'argument -r android.hardware:hardware/interfaces. Par exemple, si le package est vendor.awesome.foo@1.0::IFoo et que hidl-gen est envoyé à -r vendor.awesome:some/device/independent/path/interfaces, le fichier d'interface doit se trouver dans $ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal.

En pratique, il est recommandé qu'un fournisseur ou un OEM nommé awesome place ses interfaces standards dans vendor.awesome. Une fois qu'un chemin d'accès au package a été sélectionné, il ne doit pas être modifié, car il est intégré à l'ABI de l'interface.

Le mappage du chemin d'accès au package doit être unique

Par exemple, si vous avez -rsome.package:$PATH_A et -rsome.package:$PATH_B, $PATH_A doit être égal à $PATH_B pour un répertoire d'interface cohérent (cela facilite également le gestion des versions des interfaces).

La racine du package doit contenir un fichier de gestion des versions

Si vous créez un chemin d'accès au package tel que -r vendor.awesome:vendor/awesome/interfaces, vous devez également créer le fichier $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt, qui doit contenir des hachages d'interfaces créées à l'aide de l'option -Lhash dans hidl-gen (ce point est abordé en détail dans la section Hachage avec hidl-gen).

Les interfaces se trouvent dans des emplacements indépendants de l'appareil.

En pratique, nous vous recommandons de partager les interfaces entre les branches. Cela permet de réutiliser et de tester au maximum le code sur différents appareils et cas d'utilisation.