AIDL für HALs

Mit Android 11 wird die Möglichkeit eingeführt, AIDL für HALs in Android zu verwenden. Dadurch ist es möglich, Teile von Android ohne HIDL zu implementieren. Stellen Sie HALs nach Möglichkeit auf die ausschließliche Verwendung von AIDL um (wenn vorgelagerte HALs HIDL verwenden, muss HIDL verwendet werden).

HALs, die AIDL für die Kommunikation zwischen Framework-Komponenten wie denen in system.img und Hardwarekomponenten wie denen in vendor.img verwenden, müssen stabile AIDL verwenden. Für die Kommunikation innerhalb einer Partition, z. B. von einem HAL zu einem anderen, gibt es jedoch keine Einschränkungen für den IPC-Mechanismus.

Ziel

AIDL gibt es schon länger als HIDL und wird auch an vielen anderen Stellen verwendet, z. B. zwischen Android-Framework-Komponenten oder in Apps. Da AIDL jetzt Stabilität unterstützt, ist es möglich, einen gesamten Stack mit einer einzigen IPC-Laufzeit zu implementieren. AIDL hat auch ein besseres Versionsverwaltungssystem als HIDL.

  • Die Verwendung einer einzigen IPC-Sprache bedeutet, dass nur eins zu lernen, zu debuggen, zu optimieren und zu sichern ist.
  • AIDL unterstützt die direkte Versionsverwaltung für die Inhaber einer Schnittstelle:
    • Inhaber können Methoden am Ende von Schnittstellen oder Feldern zu Parcelables hinzufügen. Das bedeutet, dass es einfacher ist, Code im Laufe der Jahre zu versionieren, und auch die Kosten im Vergleich zum Vorjahr sind geringer (Typen können direkt geändert werden und es sind keine zusätzlichen Bibliotheken für jede Schnittstellenversion erforderlich).
    • Erweiterungsschnittstellen können zur Laufzeit statt im Typsystem hinzugefügt werden. Daher ist es nicht erforderlich, ein Rebase von Downstream-Erweiterungen auf neuere Versionen von Schnittstellen durchzuführen.
  • Eine vorhandene AIDL-Schnittstelle kann direkt verwendet werden, wenn der Inhaber sie stabilisiert. Zuvor musste eine vollständige Kopie der Schnittstelle in HIDL erstellt werden.

Build für die AIDL-Laufzeit

AIDL hat drei verschiedene Back-Ends: Java, NDK und CPP. Zur Verwendung von Stable AIDL müssen Sie immer die Systemkopie von libbinder unter system/lib*/libbinder.so verwenden und mit /dev/binder sprechen. Für Code im Anbieter-Image bedeutet dies, dass libbinder (aus dem VNDK) nicht verwendet werden kann: Diese Bibliothek hat eine instabile C++ API und instabile Interna. Stattdessen muss der Code des nativen Anbieters das NDK-Back-End von AIDL verwenden, eine Verknüpfung mit libbinder_ndk erstellen, das vom libbinder.so-System unterstützt wird, und mit den NDK-Bibliotheken verknüpfen, die von aidl_interface-Einträgen erstellt wurden. Die genauen Modulnamen finden Sie in den Benennungsregeln für Module.

AIDL HAL-Schnittstelle schreiben

Damit eine AIDL-Schnittstelle zwischen System und Anbieter verwendet werden kann, müssen an der Schnittstelle zwei Änderungen vorgenommen werden:

  • Jede Typdefinition muss mit @VintfStability annotiert werden.
  • Die Deklaration zu aidl_interface muss stability: "vintf", enthalten.

Nur der Inhaber einer Benutzeroberfläche kann diese Änderungen vornehmen.

Wenn Sie diese Änderungen vornehmen, muss die Schnittstelle im VINTF-Manifest enthalten sein, damit sie funktioniert. Testen Sie dies (und die zugehörigen Anforderungen, z. B. Prüfen, ob freigegebene Schnittstellen eingefroren sind), mit dem VTS-Test vts_treble_vintf_vendor_test. Sie können eine @VintfStability-Schnittstelle ohne diese Anforderungen verwenden, indem Sie entweder AIBinder_forceDowngradeToLocalStability im NDK-Back-End, android::Stability::forceDowngradeToLocalStability im C++-Back-End oder android.os.Binder#forceDowngradeToSystemStability im Java-Back-End in einem Binder-Objekt aufrufen, bevor es an einen anderen Prozess gesendet wird. Das Downgrade eines Dienstes auf die Anbieterstabilität wird in Java nicht unterstützt, da alle Anwendungen in einem Systemkontext ausgeführt werden.

Deaktivieren Sie außerdem das CPP-Back-End, um den Code möglichst zu übertragen und potenzielle Probleme wie unnötige zusätzliche Bibliotheken zu vermeiden.

Die Verwendung von backends im folgenden Codebeispiel ist korrekt, da es drei Back-Ends gibt (Java, NDK und CPP). Der folgende Code zeigt, wie speziell das CPP-Back-End ausgewählt wird, um es zu deaktivieren.

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

AIDL HAL-Schnittstellen finden

AOSP Stable AIDL-Schnittstellen für HALs befinden sich in denselben Basisverzeichnissen wie HIDL-Schnittstellen in aidl-Ordnern.

  • Hardware/Schnittstellen
  • Frameworks/Hardware/Schnittstellen
  • System/Hardware/Schnittstellen

Erweiterungsschnittstellen sollten in andere hardware/interfaces-Unterverzeichnisse in vendor oder hardware eingefügt werden.

Erweiterungsoberflächen

Android hat mit jedem Release eine Reihe von offiziellen AOSP-Oberflächen. Wenn Android-Partner diesen Oberflächen Funktionen hinzufügen möchten, sollten sie diese nicht direkt ändern, da dies bedeutet, dass ihre Android-Laufzeit nicht mit der AOSP-Android-Laufzeit kompatibel ist. Bei GMS-Geräten muss außerdem sichergestellt werden, dass das GSI-Image weiterhin funktioniert, ohne dass diese Schnittstellen geändert werden müssen.

Erweiterungen können auf zwei verschiedene Arten registriert werden:

Es wird jedoch eine Erweiterung registriert. Wenn jedoch anbieterspezifische Komponenten (d. h., die nicht Teil der vorgelagerten AOSP-Komponenten sind) die Schnittstelle verwenden, besteht keine Möglichkeit eines Zusammenführungskonflikts. Wenn jedoch nachgelagerte Änderungen an vorgelagerten AOSP-Komponenten vorgenommen werden, kann es zu Zusammenführungskonflikten kommen. Die folgenden Strategien werden empfohlen:

  • Die Ergänzungen der Benutzeroberfläche können im nächsten Release an AOSP übertragen werden.
  • Ergänzungen der Benutzeroberfläche, die mehr Flexibilität ohne Zusammenführungskonflikte ermöglichen, können im nächsten Release vorgeschaltet werden.

Erweiterungsparcelables: ParcelableHolder

ParcelableHolder ist eine Parcelable, die ein weiteres Parcelable enthalten kann. Der Hauptanwendungsfall von ParcelableHolder besteht darin, eine Parcelable erweiterbar zu machen. Beispiel für ein Image, von dem Geräteimplementierungen erwarten, dass sie eine AOSP-definierte Parcelable (AospDefinedParcelable) erweitern können, um ihre Mehrwertfunktionen einzubeziehen.

Bisher konnten Geräteimplementierungen ohne ParcelableHolder eine AOSP-definierte stabile AIDL-Schnittstelle nicht ändern, da das Hinzufügen weiterer Felder ein Fehler wäre:

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

Wie im vorherigen Code zu sehen ist, funktioniert diese Vorgehensweise nicht, da die vom Geräte-Implementierer hinzugefügten Felder einen Konflikt verursachen können, wenn das Parcelable in den nächsten Android-Versionen überarbeitet wird.

Mit ParcelableHolder kann der Inhaber eines Pakets einen Erweiterungspunkt in einer Parcelable definieren.

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

Anschließend können die Geräteimplementierungen ihre eigene Parcelable für ihre Erweiterung definieren.

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

Schließlich kann das neue Parcelable-Element mit dem Feld ParcelableHolder an das ursprüngliche Parcelable-Element angehängt werden.


// 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>();

AIDL HAL-Serverinstanznamen

Konventionsgemäß haben AIDL HAL-Dienste einen Instanznamen im Format $package.$type/$instance. Beispielsweise wird eine Instanz des Vibrations-HAL als android.hardware.vibrator.IVibrator/default registriert.

AIDL HAL-Server schreiben

AIDL-Server vom Typ @VintfStability müssen im VINTF-Manifest deklariert werden. Beispiel:

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

Andernfalls sollte der AIDL-Dienst normal registriert werden. Beim Ausführen von VTS-Tests ist davon auszugehen, dass alle deklarierten AIDL HALs verfügbar sind.

AIDL-Client schreiben

AIDL-Clients müssen sich in der Kompatibilitätsmatrix deklarieren. Beispiel:

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

Vorhandene HAL von HIDL in AIDL konvertieren

Verwenden Sie das hidl2aidl-Tool, um eine HIDL-Schnittstelle in AIDL zu konvertieren.

hidl2aidl-Funktionen:

  • .aidl-Dateien basierend auf den .hal-Dateien für das angegebene Paket erstellen
  • Build-Regeln für das neu erstellte AIDL-Paket mit allen aktivierten Back-Ends erstellen
  • Erstellen von Übersetzungsmethoden in den Java-, CPP- und NDK-Back-Ends für die Übersetzung von den HIDL- in die AIDL-Typen
  • Build-Regeln für Übersetzungsbibliotheken mit erforderlichen Abhängigkeiten erstellen
  • Erstellen Sie statische Assertions, um sicherzustellen, dass HIDL- und AIDL-Enumeratoren in den CPP- und NDK-Back-Ends dieselben Werte haben

Führen Sie die folgenden Schritte aus, um ein Paket mit .hal-Dateien in AIDL-Dateien zu konvertieren:

  1. Erstellen Sie das Tool in system/tools/hidl/hidl2aidl.

    Wenn Sie dieses Tool aus der neuesten Quelle erstellen, ist der Funktionsumfang am umfangreichsten. Sie können die neueste Version verwenden, um Schnittstellen in älteren Branches aus früheren Releases zu konvertieren.

    m hidl2aidl
    
  2. Führen Sie das Tool mit einem Ausgabeverzeichnis aus, gefolgt vom zu konvertierenden Paket.

    Optional können Sie mit dem Argument -l den Inhalt einer neuen Lizenzdatei oben in allen generierten Dateien einfügen. Achten Sie darauf, die richtige Lizenz und das richtige Datum zu verwenden.

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

    Beispiel:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Lesen Sie sich die generierten Dateien durch und beheben Sie eventuelle Probleme mit der Konvertierung.

    • conversion.log enthält alle unbehandelten Probleme, die zuerst behoben werden müssen.
    • Die generierten .aidl-Dateien enthalten möglicherweise Warnungen und Vorschläge, für die möglicherweise Maßnahmen erforderlich sind. Diese Kommentare beginnen mit //.
    • Nutzen Sie die Gelegenheit, bereinigen und verbessern Sie das Paket.
    • Prüfen Sie die Annotation @JavaDerive auf Features, die möglicherweise erforderlich sind, z. B. toString oder equals.
  4. Erstellen Sie nur die Ziele, die Sie benötigen.

    • Deaktivieren Sie Back-Ends, die nicht verwendet werden. Das NDK-Back-End gegenüber dem CPP-Back-End bevorzugen, siehe Laufzeit auswählen
    • Entfernen Sie Übersetzungsbibliotheken und jeglichen generierten Code, der nicht verwendet wird.
  5. Siehe Wichtige Unterschiede zu AIDL/HIDL.

    • Die Verwendung der integrierten Status und Ausnahmen von AIDL verbessern in der Regel die Oberfläche und macht einen anderen schnittstellenspezifischen Statustyp überflüssig.
    • AIDL-Schnittstellenargumente in Methoden sind standardmäßig nicht @nullable, wie dies in HIDL der Fall wäre.

SEPolicy für AIDL HALs

Ein für den Anbietercode sichtbarer AIDL-Diensttyp muss das Attribut hal_service_type haben. Andernfalls ist die sepolicy-Konfiguration mit der für jeden anderen AIDL-Dienst identisch (obwohl es spezielle Attribute für HALs gibt). Hier ist eine Beispieldefinition eines HAL-Dienstkontexts:

    type hal_foo_service, service_manager_type, hal_service_type;

Bei den meisten von der Plattform definierten Diensten wird bereits ein Dienstkontext mit dem richtigen Typ hinzugefügt (z. B. wäre android.hardware.foo.IFoo/default bereits als hal_foo_service gekennzeichnet). Wenn ein Framework-Client jedoch mehrere Instanznamen unterstützt, müssen in gerätespezifischen service_contexts-Dateien zusätzliche Instanznamen hinzugefügt werden.

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

HAL-Attribute müssen hinzugefügt werden, wenn ein neuer HAL-Typ erstellt wird. Ein bestimmtes HAL-Attribut kann mehreren Diensttypen zugeordnet sein (jeder Dienst kann, wie gerade erläutert), mehrere Instanzen haben. Für einen HAL, foo, haben wir hal_attribute(foo). Mit diesem Makro werden die Attribute hal_foo_client und hal_foo_server definiert. Bei einer bestimmten Domain verknüpfen die Makros hal_client_domain und hal_server_domain eine Domain mit einem bestimmten HAL-Attribut. Beispielsweise entspricht der Systemserver, der ein Client dieses HAL ist, der Richtlinie hal_client_domain(system_server, hal_foo). Ein HAL-Server enthält ähnlich wie hal_server_domain(my_hal_domain, hal_foo). In der Regel erstellen wir für ein bestimmtes HAL-Attribut auch eine Domain wie hal_foo_default als Referenz- oder Beispiel-HALs. Einige Geräte verwenden diese Domains jedoch als eigene Server. Die Unterscheidung zwischen Domains für mehrere Server ist nur wichtig, wenn wir mehrere Server haben, die dieselbe Schnittstelle bedienen und einen anderen Berechtigungssatz in ihrer Implementierung benötigen. In all diesen Makros ist hal_foo kein wirklich ein sepolicy-Objekt. Stattdessen wird dieses Token von diesen Makros verwendet, um auf die Gruppe von Attributen zu verweisen, die einem Client-Server-Paar zugeordnet sind.

Bisher haben wir jedoch hal_foo_service und hal_foo (das Attributpaar aus hal_attribute(foo)) nicht verknüpft. Ein HAL-Attribut wird über das hal_attribute_service-Makro mit AIDL HAL-Diensten verknüpft (HIDL-HALs verwenden das Makro hal_attribute_hwservice). Beispiel: hal_attribute_service(hal_foo, hal_foo_service). Dies bedeutet, dass hal_foo_client-Prozesse den HAL abrufen und hal_foo_server-Prozesse den HAL registrieren können. Die Erzwingung dieser Registrierungsregeln wird vom Kontextmanager (servicemanager) durchgeführt. Beachten Sie, dass Dienstnamen möglicherweise nicht immer HAL-Attributen entsprechen. So könnte beispielsweise hal_attribute_service(hal_foo, hal_foo2_service) angezeigt werden. Da dies bedeutet, dass die Dienste immer zusammen verwendet werden, können wir im Allgemeinen hal_foo2_service entfernen und hal_foo_service für alle unsere Dienstkontexte verwenden. Die meisten HALs, für die mehrere hal_attribute_service festgelegt werden, sind darauf zurückzuführen, dass der ursprüngliche HAL-Attributname nicht allgemein genug ist und nicht geändert werden kann.

Zusammenfassend sieht ein HAL-Beispiel wie folgt aus:

    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)

Angehängte Erweiterungsoberflächen

Eine Erweiterung kann an jede Binder-Schnittstelle angehängt werden, unabhängig davon, ob es sich um eine Schnittstelle der obersten Ebene handelt, die direkt beim Dienstmanager registriert ist, oder um eine Subschnittstelle. Wenn Sie eine Erweiterung erhalten, müssen Sie bestätigen, dass der Typ der Erweiterung den Erwartungen entspricht. Erweiterungen können nur über den Prozess festgelegt werden, in dem ein Binder bereitgestellt wird.

Angehängte Erweiterungen sollten immer dann verwendet werden, wenn eine Erweiterung die Funktionalität eines vorhandenen HAL ändert. Wenn völlig neue Funktionen erforderlich sind, muss dieser Mechanismus nicht verwendet werden. Eine Erweiterungsschnittstelle kann direkt beim Dienstmanager registriert werden. Angehängte Erweiterungsschnittstellen sind am sinnvollsten in Verbindung mit Subschnittstellen, da diese Hierarchien tief oder mehrfach instanziiert sein können. Wenn Sie eine globale Erweiterung verwenden, um die Hierarchie der Binder-Schnittstelle eines anderen Dienstes zu spiegeln, wäre eine umfassende Buchhaltung erforderlich, um die gleichwertigen Funktionen für direkt angehängte Erweiterungen bereitzustellen.

Verwenden Sie die folgenden APIs, um eine Erweiterung für Binder festzulegen:

  • Im NDK-Back-End: AIBinder_setExtension
  • Im Java-Back-End: android.os.Binder.setExtension
  • Im CPP-Back-End: android::Binder::setExtension
  • Im Rust-Back-End: binder::Binder::set_extension

Verwenden Sie die folgenden APIs, um eine Erweiterung für einen Binder zu erhalten:

  • Im NDK-Back-End: AIBinder_getExtension
  • Im Java-Back-End: android.os.IBinder.getExtension
  • Im CPP-Back-End: android::IBinder::getExtension
  • Im Rust-Back-End: binder::Binder::get_extension

Weitere Informationen zu diesen APIs finden Sie in der Dokumentation zur Funktion getExtension im entsprechenden Back-End. Ein Beispiel für die Verwendung von Erweiterungen finden Sie unter hardware/interfaces/tests/extension/vibrator.

Wesentliche AIDL- und HIDL-Unterschiede

Beachten Sie bei der Verwendung von AIDL HALs oder AIDL HAL-Schnittstellen die Unterschiede im Vergleich zum Schreiben von HIDL HALs.

  • Die Syntax der AIDL-Sprache kommt der Java-Syntax näher. Die HIDL-Syntax ähnelt der C++-Syntax.
  • Alle AIDL-Schnittstellen haben integrierte Fehlerstatus. Anstatt benutzerdefinierte Statustypen zu erstellen, erstellen Sie in Schnittstellendateien konstante Status-intts und verwenden Sie EX_SERVICE_SPECIFIC in den CPP/NDK-Back-Ends und ServiceSpecificException im Java-Back-End. Siehe Fehlerbehandlung.
  • AIDL startet Threadpools nicht automatisch, wenn Binderobjekte gesendet werden. Sie müssen manuell gestartet werden (siehe Thread-Verwaltung).
  • AIDL bricht bei ungeprüften Transportfehlern nicht ab (HIDL Return bricht bei nicht aktivierten Fehlern ab).
  • AIDL kann nur einen Typ pro Datei deklarieren.
  • AIDL-Argumente können zusätzlich zum Ausgabeparameter als Ein-/Ausgangs-/Inout-Argumente angegeben werden. Es gibt keine "synchronen Callbacks".
  • AIDL verwendet „fd“ anstelle von „Handle“ als primitiven Typ.
  • HIDL verwendet Hauptversionen für inkompatible Änderungen und Nebenversionen für kompatible Änderungen. In AIDL werden abwärtskompatible Änderungen direkt vorgenommen. AIDL hat kein explizites Konzept von Hauptversionen. Diese wird stattdessen in Paketnamen eingebunden. AIDL könnte beispielsweise den Paketnamen bluetooth2 verwenden.
  • AIDL übernimmt nicht standardmäßig die Echtzeitpriorität. Die Funktion setInheritRt muss pro Binder verwendet werden, um die Prioritätsübernahme in Echtzeit zu ermöglichen.