AIDL dla licencji HAL

W Androidzie 11 można korzystać z AIDL w przypadku list HAL na Androidzie. Umożliwia to wdrożenie części Androida bez HIDL. Przeniesienie HAL do korzystania wyłącznie z AIDL w miarę możliwości (gdy HAL nadrzędne używają HIDL, należy używać HIDL).

Listy HAL korzystające z AIDL do komunikacji między komponentami platformy, takimi jak system.img, a komponentami sprzętowymi, takimi jak vendor.img, muszą używać stabilnej wersji AIDL. Jednak na potrzeby komunikacji w ramach partycji, na przykład z jednej HAL do drugiej, nie ma ograniczeń w stosowaniu mechanizmu IPC.

Motywacja

Protokół AIDL jest stosowany dłużej niż HIDL i jest używany w wielu innych miejscach, np. między komponentami platformy Androida lub aplikacjami. Dzięki stabilności AIDL można wdrożyć cały stos w jednym środowisku wykonawczym IPC. AIDL ma również lepszy system obsługi wersji niż HIDL.

  • Korzystanie z jednego języka IPC oznacza, że do nauki, debugowania, optymalizacji i bezpieczeństwa potrzebne są tylko te informacje.
  • AIDL obsługuje obsługę wersji w miejscu dla właścicieli interfejsu:
    • Właściciele mogą dodawać metody na końcu interfejsów lub w polach pakietów. Oznacza to, że tworzenie wersji kodu z biegiem lat jest łatwiejsze, a poza tym koszt roczny jest mniejszy (rodzaje można wprowadzać na miejscu i nie trzeba dodawać dodatkowych bibliotek do każdej wersji interfejsu).
    • Interfejsy rozszerzeń można dołączać w czasie działania, a nie w systemie typów, więc nie ma potrzeby ponownego wdrażania kolejnych rozszerzeń w nowszych wersjach interfejsów.
  • Istniejącego interfejsu AIDL można używać bezpośrednio, jeśli jego właściciel zdecyduje się go ustabilizować. Wcześniej całą kopię interfejsu trzeba było utworzyć w HIDL.

Kompilacja na podstawie środowiska wykonawczego AIDL

AIDL ma 3 różne backendy: Java, NDK i CPP. Aby używać stabilnej wersji AIDL, musisz zawsze używać systemowej kopii biblioteki libbinder dostępnej pod adresem system/lib*/libbinder.so i rozmawiać na stronie /dev/binder. W przypadku kodu obrazu dostawcy oznacza to, że nie można użyć pliku libbinder (z VNDK): ta biblioteka ma niestabilny interfejs API C++ i niestabilne komponenty wewnętrzne. Zamiast tego natywny kod dostawcy musi korzystać z backendu NDK AIDL, łączyć się z zasadą libbinder_ndk (wspieraną przez system libbinder.so) i porównywać z bibliotekami NDK utworzonymi przez wpisy aidl_interface. Dokładne nazwy modułów znajdziesz w artykule z regułami nazewnictwa modułów.

Pisanie interfejsu AIDL HAL

Aby można było używać interfejsu AIDL między systemem a dostawcą, interfejs wymaga 2 zmian:

  • Do każdej definicji typu należy dodać adnotację @VintfStability.
  • Deklaracja aidl_interface musi zawierać stability: "vintf",.

Tylko właściciel interfejsu może wprowadzić te zmiany.

Aby te zmiany działały, interfejs musi zawierać plik manifestu VINTF. Przetestuj to (i powiązane z nimi wymagania, takie jak sprawdzenie, czy opublikowane interfejsy są zablokowane) za pomocą testu VTS vts_treble_vintf_vendor_test. Możesz używać interfejsu @VintfStability bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability w backendzie NDK, android::Stability::forceDowngradeToLocalStability w backendzie C++ lub android.os.Binder#forceDowngradeToSystemStability w backendzie Java w obiekcie Binder przed wysłaniem go do innego procesu. Przejście z usługi na stabilność dostawcy nie jest obsługiwane w języku Java, ponieważ wszystkie aplikacje działają w kontekście systemowym.

Aby maksymalnie zwiększyć możliwość przenoszenia kodu i uniknąć potencjalnych problemów, takich jak niepotrzebne biblioteki dodatkowe, wyłącz backend CPP.

Zwróć uwagę, że użycie backends w poniższym przykładzie kodu jest prawidłowe, ponieważ istnieją 3 backendy (Java, NDK i CPP). Poniższy kod pokazuje, jak wybrać backend CPP, aby go wyłączyć.

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

Znajdź interfejsy AIDL HAL

Interfejsy AOSP Stable AIDL dla HAL znajdują się w tych samych katalogach podstawowych co interfejsy HiDL w folderach aidl.

  • sprzęt/interfejsy
  • platformy/sprzęt/interfejsy
  • system/sprzęt/interfejsy

Interfejsy rozszerzeń umieść w innych podkatalogach hardware/interfaces w katalogu vendor lub hardware.

Interfejsy rozszerzeń

Każda wersja Androida ma zestaw oficjalnych interfejsów AOSP. Gdy partnerzy Androida chcą dodać funkcje do tych interfejsów, nie powinni zmieniać ich bezpośrednio, ponieważ oznaczałoby to, że ich środowisko wykonawcze Androida jest niezgodne ze środowiskiem wykonawczym AOSP na Androidzie. W przypadku urządzeń GMS unikanie zmiany tych interfejsów sprawia też, że obraz GSI będzie nadal działać.

Rozszerzenia mogą się rejestrować na 2 sposoby:

  • w czasie działania aplikacji znajdziesz w artykule o dołączonych rozszerzeniach.
  • samodzielny, zarejestrowany na całym świecie i w systemie VINTF.

Rozszerzenie jest jednak zarejestrowane, gdy specyficzne dla dostawcy (czyli nie jest ono częścią nadrzędnych komponentów AOSP) z interfejsu, więc nie ma możliwości wystąpienia konfliktu między nimi. Jednak w przypadku wprowadzenia zmian w nadrzędnych komponentach AOSP może wystąpić konflikt scalania. Zalecamy stosowanie tych strategii:

  • dodatki do interfejsu można przesłać do AOSP w kolejnej wersji
  • Dodatki do interfejsu, które zapewniają większą elastyczność, bez konfliktów scalania, można wprowadzić w następnej wersji

Rozszerzenie parcelables: ParcelableHolder

ParcelableHolder to typ elementu Parcelable, który może zawierać inny element Parcelable. Głównym przypadkiem użycia funkcji ParcelableHolder jest możliwość rozszerzenia Parcelable. Może to być na przykład obraz, którego osoby zajmujące się implementacją urządzeń chcą rozszerzyć zakres Parcelable (AospDefinedParcelable) zdefiniowany przez AOSP, aby uwzględnić funkcje zwiększające wartość.

Wcześniej bez funkcji ParcelableHolder osoby implementujące urządzenia nie mogły modyfikować stabilnego interfejsu AIDL zdefiniowanego przez AOSP, ponieważ dodawanie kolejnych pól powodowałoby błąd:

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

Jak zaobserwowaliśmy w poprzednim kodzie, ta praktyka jest uszkodzona, ponieważ pola dodane przez narzędzie implementujące urządzenie mogą zawierać konflikt, gdy w następnych wersjach Androida zostanie poprawiony interfejs Parcelable.

Za pomocą ParcelableHolder właściciel nieruchomości może zdefiniować punkt rozszerzenia w Parcelable.

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

Następnie osoby implementujące urządzenia mogą zdefiniować własne Parcelable dla rozszerzenia.

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

I wreszcie nową usługę Parcelable możesz dołączyć do pierwotnego elementu Parcelable przy użyciu pola 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>();

Nazwy instancji serwera AIDL HAL

Zgodnie z konwencją usługi AIDL HAL mają nazwę instancji w formacie $package.$type/$instance. Na przykład wystąpienie HAL wibracji jest zarejestrowane jako android.hardware.vibrator.IVibrator/default.

Zapisywanie serwera HAL AIDL

@VintfStability Serwery AIDL muszą być zadeklarowane w pliku manifestu VINTF, na przykład:

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

W przeciwnym razie powinni zarejestrować usługę AIDL w zwykły sposób. Podczas uruchamiania testów VTS powinny być dostępne wszystkie zadeklarowane wartości HAL AIDL.

Zapisywanie klienta AIDL

Klienci AIDL muszą zadeklarować się w macierzy zgodności, na przykład tak:

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

Konwertowanie istniejącej usługi HAL z HIDL na AIDL

Za pomocą narzędzia hidl2aidl konwertuj interfejs HIDL na AIDL.

Funkcje usługi hidl2aidl:

  • Utwórz pliki typu .aidl na podstawie plików typu .hal w danym pakiecie
  • Utwórz reguły kompilacji dla nowo utworzonego pakietu AIDL z włączonymi wszystkimi backendami
  • Twórz metody translacji w backendach Java, CPP i NDK na potrzeby translacji z typów HIDL na AIDL
  • Tworzenie reguł kompilacji dla bibliotek translacji z wymaganymi zależnościami
  • Utwórz asercje statyczne, aby mieć pewność, że moduły wyliczające HIDL i AIDL mają te same wartości w backendach CPP i NDK

Aby przekonwertować pakiet plików .hal na pliki .aidl, wykonaj te czynności:

  1. Skompiluj narzędzie w obszarze system/tools/hidl/hidl2aidl.

    Tworzenie tego narzędzia na podstawie najnowszego źródła zapewnia największą funkcjonalność. Możesz używać najnowszej wersji do konwertowania interfejsów w starszych gałęziach z poprzednich wersji.

    m hidl2aidl
    
  2. Uruchom narzędzie z katalogiem wyjściowym, a następnie z pakietem do przekonwertowania.

    Opcjonalnie za pomocą argumentu -l możesz dodać zawartość nowego pliku licencji na początku wszystkich wygenerowanych plików. Użyj właściwej licencji i daty.

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

    Na przykład:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Przeczytaj wygenerowane pliki i rozwiąż wszystkie problemy z konwersją.

    • conversion.log zawiera nierozwiązane problemy do rozwiązania.
    • Wygenerowane pliki .aidl mogą zawierać ostrzeżenia i sugestie, które mogą wymagać podjęcia działania. Zaczynają się one od: //.
    • Skorzystaj z okazji, by wyczyścić pakiet i go ulepszyć.
    • Sprawdź adnotację @JavaDerive, by zobaczyć, czy są potrzebne funkcje, np. toString lub equals.
  4. Twórz tylko te cele, których potrzebujesz.

    • Wyłącz backendy, które nie będą używane. Wybieraj backend NDK zamiast backendu CPP. Więcej informacji znajdziesz w sekcji o wybieraniu środowiska wykonawczego.
    • Usuń biblioteki translacji i wygenerowany przez nie kod, który nie będzie używany.
  5. Zobacz Główne różnice w AIDL i HIDL.

    • Korzystanie z wbudowanych Status i wyjątków AIDL zazwyczaj ulepsza interfejs i eliminuje potrzebę stosowania innego typu stanu zależnego od interfejsu.
    • Argumenty interfejsu AIDL w metodach nie mają domyślnie wartości @nullable, jak były w HIDL.

SEPolicy dla klientów HAL AIDL

Typ usługi AIDL, który jest widoczny dla kodu dostawcy, musi mieć atrybut hal_service_type. Poza tym konfiguracja sepolicy jest taka sama jak w przypadku każdej innej usługi AIDL (choć istnieją specjalne atrybuty dla HAL). Oto przykładowa definicja kontekstu usługi HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

W przypadku większości usług zdefiniowanych przez platformę dodano już kontekst usługi w prawidłowym typie (na przykład android.hardware.foo.IFoo/default byłby już oznaczony jako hal_foo_service). Jeśli jednak klient platformy obsługuje wiele nazw instancji, należy dodać dodatkowe nazwy w plikach service_contexts przeznaczonych na konkretne urządzenia.

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

Atrybuty HAL należy dodać podczas tworzenia nowego typu HAL. Konkretny atrybut HAL może być powiązany z wieloma typami usług (z których każdy może mieć kilka instancji, jak omówiliśmy przed chwilą). W przypadku HAL foo mamy hal_attribute(foo). To makro definiuje atrybuty hal_foo_client i hal_foo_server. W przypadku danej domeny makra hal_client_domain i hal_server_domain łączą domenę z danym atrybutem HAL. Na przykład: serwer systemu będący klientem tej HAL odpowiada zasadzie hal_client_domain(system_server, hal_foo). Serwer HAL zawiera w podobny sposób hal_server_domain(my_hal_domain, hal_foo). Zwykle dla danego atrybutu HAL tworzymy również domenę, np. hal_foo_default, lub przykładową listę HAL. Niektóre urządzenia używają jednak tych domen na potrzeby własnych serwerów. Odróżnianie domen od wielu serwerów ma znaczenie tylko wtedy, gdy mamy kilka serwerów, które obsługują ten sam interfejs, a ich implementacje wymagają różnych uprawnień. We wszystkich tych makrach hal_foo nie jest obiektem sepolicy. Ten token jest używany przez te makra do odwoływania się do grupy atrybutów powiązanych z parą serwerów klienta.

Jednak jak do tej pory nie powiązaliśmy żadnych atrybutów hal_foo_service i hal_foo (czyli pary atrybutów z hal_attribute(foo)). Atrybut HAL jest powiązany z usługami AIDL HAL za pomocą makra hal_attribute_service (HAL HAL używa makra hal_attribute_hwservice). Przykład: hal_attribute_service(hal_foo, hal_foo_service). Oznacza to, że procesy hal_foo_client mogą rejestrować HAL, a procesy hal_foo_server – rejestrować tę wartość. Egzekwowanie tych reguł rejestracji odpowiada menedżer kontekstu (servicemanager). Uwaga: nazwy usług nie zawsze odpowiadają atrybutom HAL. Na przykład możemy zobaczyć hal_attribute_service(hal_foo, hal_foo2_service). Zasadniczo oznacza to jednak, że usługi są zawsze używane razem, dlatego możemy rozważyć usunięcie obiektu hal_foo2_service i użycie hal_foo_service we wszystkich kontekstach naszych usług. Większość identyfikatorów HAL z wieloma atrybutami hal_attribute_service wynika z tego, że pierwotna nazwa atrybutu HAL nie jest wystarczająco ogólna i nie można jej zmienić.

Po połączeniu wszystkich tych danych przykład HAL wygląda tak:

    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)

Interfejsy podłączonych rozszerzeń

Rozszerzenie można dołączyć do dowolnego interfejsu Binder, niezależnie od tego, czy jest to interfejs najwyższego poziomu zarejestrowany bezpośrednio przez menedżera usługi czy interfejs podrzędny. Gdy pobierasz rozszerzenie, musisz sprawdzić, czy jego typ jest zgodny z oczekiwaniami. Rozszerzenia można skonfigurować tylko podczas procesu udostępniającego segregator.

Załączonych rozszerzeń należy używać zawsze, gdy rozszerzenie modyfikuje działanie istniejącej HAL. Nie trzeba używać tego mechanizmu, gdy potrzebna jest zupełnie nowa funkcja. Interfejs rozszerzenia można zarejestrować bezpośrednio u menedżera usługi. Interfejsy podłączonych rozszerzeń działają najlepiej, gdy są dołączone do podinterfejsów, ponieważ te hierarchie mogą być głębokie lub wieloinstancyjne. Użycie rozszerzenia globalnego w celu odzwierciedlenia hierarchii interfejsu Binder innej usługi wymagałoby szeroko zakrojonej księgowości, aby zapewnić dostęp do funkcji równoważnych bezpośrednio rozszerzeniom.

Aby skonfigurować rozszerzenie w narzędziu do segregatora, użyj tych interfejsów API:

  • W backendzie NDK: AIBinder_setExtension
  • W backendzie Javy: android.os.Binder.setExtension
  • W backendzie CPP: android::Binder::setExtension
  • W backendzie Rust: binder::Binder::set_extension

Aby uzyskać rozszerzenie do separatora, użyj tych interfejsów API:

  • W backendzie NDK: AIBinder_getExtension
  • W backendzie Javy: android.os.IBinder.getExtension
  • W backendzie CPP: android::IBinder::getExtension
  • W backendzie Rust: binder::Binder::get_extension

Więcej informacji na temat tych interfejsów API znajdziesz w dokumentacji funkcji getExtension w odpowiednim backendzie. Przykład korzystania z rozszerzeń znajdziesz w artykule hardware/interfaces/tests/extension/vibrator.

Główne różnice w AIDL i HIDL

Podczas korzystania z komponentów HAL AIDL lub interfejsów AIDL HAL pamiętaj o różnicach w porównaniu z tworzeniem wartości HAL HIDL.

  • Składnia języka AIDL jest bliższa Javie. Składnia HIDL jest podobna do języka C++.
  • Wszystkie interfejsy AIDL mają wbudowane stany błędu. Zamiast tworzyć niestandardowe typy stanu, twórz stałe inty stanu w plikach interfejsu i używaj polecenia EX_SERVICE_SPECIFIC w backendach CPP/NDK oraz ServiceSpecificException w backendzie Java. Patrz sekcja Obsługa błędów.
  • AIDL nie uruchamia automatycznie pul wątków po wysłaniu obiektów powiązań. Należy je uruchamiać ręcznie (zobacz zarządzanie wątkami).
  • AIDL nie zatrzymuje danych w przypadku niezaznaczonych błędów transportu (Return HIDL przerywa działanie w przypadku niezaznaczonych błędów).
  • AIDL może zadeklarować tylko 1 typ na plik.
  • Oprócz parametru wyjściowego argumenty AIDL można określić jako wejścia/wyjścia (nie ma synchronicznych wywołań zwrotnych).
  • AIDL używa typu podstawowego fd zamiast uchwytu.
  • HIDL używa wersji głównych w przypadku niezgodnych zmian i wersji podrzędnych w przypadku zgodnych zmian. W AIDL wprowadzane są zmiany zgodne wstecznie. AIDL nie ma jasno określonej koncepcji wersji głównych. Zamiast tego znajduje się ona w nazwach pakietów. Na przykład AIDL może używać nazwy pakietu bluetooth2.
  • AIDL domyślnie nie dziedziczy priorytetu w czasie rzeczywistym. Aby włączyć dziedziczenie priorytetów w czasie rzeczywistym, należy używać funkcji setInheritRt w przypadku powiązania.