Obsługa wersji interfejsu

HIDL wymaga, aby każdy interfejs napisany w HIDL był oznaczony wersją. Po opublikowaniu interfejs HAL jest zablokowany i wszystkie dalsze zmiany muszą być wprowadzane w nowej wersji tego interfejsu. Opublikowanego interfejsu nie można modyfikować, ale można go rozszerzyć o inny interfejs.

Struktura kodu HIDL

Kod HIDL jest zorganizowany w zdefiniowane przez użytkownika typy, interfejsy i pakiety:

  • Typy zdefiniowane przez użytkownika (UDT). HIDL zapewnia dostęp do zestawu prostych typów danych, które można wykorzystać do tworzenia bardziej złożonych typów za pomocą struktur, zjednoczeń i wyliczeń. UDT są przekazywane do metod interfejsów i mogą być definiowane na poziomie pakietu (wspólnego dla wszystkich interfejsów) lub lokalnie dla interfejsu.
  • Interfejsy. Jako podstawowy element HIDL interfejs składa się z typów niestandardowych i deklaracji metod. Interfejsy mogą też dziedziczyć po innym interfejsie.
  • Pakiety. porządkuje powiązane interfejsy HIDL oraz typy danych, z którymi pracują; Pakiet jest identyfikowany przez nazwę i wersję oraz zawiera:
    • Plik definicji typu danych o nazwie types.hal.
    • 0 lub więcej interfejsów, każdy w oddzielnym pliku .hal.

Plik definicji typu danych types.hal zawiera tylko typy danych niestandardowych (wszystkie typy danych niestandardowych na poziomie pakietu są przechowywane w jednym pliku). Reprezentacje w języku docelowym są dostępne we wszystkich interfejsach w pakiecie.

Filozofia obsługi wersji

Pakiet HIDL (np. android.hardware.nfc), po opublikowaniu w przypadku danej wersji (np. 1.0) jest niezmienny i nie można go zmienić. Modyfikacje interfejsów w pakiecie lub zmiany w jego UDT mogą być wprowadzane tylko w innym pakiecie.

W HIDL wersjonowanie jest stosowane na poziomie pakietu, a nie interfejsu. Wszystkie interfejsy i typy danych użytkownika w pakiecie mają tę samą wersję. Wersje pakietów są zgodne z semantycznym wersjonowaniem bez poziomu poprawki i elementów metadanych kompilacji. W danym pakiecie przejście na wersję mniejszą oznacza, że nowa wersja pakietu jest zgodna ze starszą wersją, a przejście na wersję większą oznacza, że nowa wersja pakietu nie jest zgodna wstecznie ze starszą wersją.

Koncepcyjnie pakiet może być powiązany z innym pakietem na kilka sposobów:

  • Wcale nie.
  • Zgodność wsteczna na poziomie pakietu. Dzieje się tak w przypadku nowych wersji podwyższających wersję podrzędną (następna wersja zwiększona) pakietu. Nowy pakiet ma tę samą nazwę i wersję główną co stary, ale wyższą wersję podrzędną. Pod względem funkcjonalności nowy pakiet jest superzbiorem starego pakietu, co oznacza, że:
    • Nowy pakiet zawiera interfejsy najwyższego poziomu pakietu nadrzędnego, ale mogą one zawierać nowe metody, nowe typy danych użytkownika w interfejsie (rozszerzenie poziomu interfejsu opisane poniżej) i nowe typy danych użytkownika w types.hal.
    • Nowe pakiety mogą też zawierać nowe interfejsy.
    • Nowy pakiet zawiera wszystkie typy danych pakietu nadrzędnego i może obsługiwać metody (opcjonalnie ponownie zaimplementowane) ze starego pakietu.
    • Można też dodawać nowe typy danych, aby używać ich w ramach nowych metod uaktualniania dotychczasowych interfejsów lub w ramach nowych interfejsów.
  • Rozszerzalność na poziomie interfejsu z zachowaniem zgodności wstecznej. Nowy pakiet może też rozszerzać oryginalny pakiet, ponieważ składa się z logicznie oddzielnych interfejsów, które po prostu zapewniają dodatkowe funkcje, a nie główną. W tym celu warto:
    • Interfejsy w nowym pakiecie muszą korzystać z typów danych starego pakietu.
    • Interfejsy w nowym pakiecie mogą rozszerzać interfejsy co najmniej 1 starego pakietu.
  • Rozszerzyć pierwotną niekompatybilność wsteczną. Jest to nowa wersja pakietu w wersji głównej, więc nie musi być między nimi żadnego powiązania. W takim przypadku można użyć kombinacji typów ze starszej wersji pakietu i odziedziczenia podzbioru interfejsów starszych pakietów.

Struktura interfejsu

W przypadku dobrze ustrukturyzowanego interfejsu dodanie nowych typów funkcji, które nie są częścią oryginalnego projektu, nie powinno wymagać modyfikacji interfejsu HIDL. Jeśli natomiast możesz lub zamierzasz wprowadzić zmiany po obu stronach interfejsu, które wprowadzają nowe funkcje bez zmiany samego interfejsu, oznacza to, że interfejs nie jest uporządkowany.

Treble obsługuje oddzielnie skompilowane komponenty dostawcy i systemu, w których vendor.img na urządzeniu i system.img mogą być kompilowane oddzielnie. Wszystkie interakcje między vendor.imgsystem.img muszą być wyraźnie i dokładnie zdefiniowane, aby mogły działać przez wiele lat. Obejmuje to wiele interfejsów API, ale głównym interfejsem jest mechanizm IPC używany przez HIDL do komunikacji między procesami na granicy system.img/vendor.img.

Wymagania

Wszystkie dane przekazywane przez HIDL muszą być zdefiniowane w sposób jednoznaczny. Aby zapewnić, że implementacja i klient będą mogły nadal współpracować nawet wtedy, gdy są kompilowane osobno lub rozwijane niezależnie, dane muszą spełniać te wymagania:

  • Może być opisany bezpośrednio w HIDL (za pomocą struktur, typów itp.) za pomocą semantycznych nazw i znaczeni.
  • może być opisany za pomocą standardu publicznego, takiego jak ISO/IEC 7816;
  • Może być opisany za pomocą standardu sprzętowego lub fizycznej konfiguracji sprzętu.
  • W razie potrzeby mogą to być dane nieprzejrzyste (np. klucze publiczne, identyfikatory itp.).

Jeśli używane są dane nieprzezroczyste, muszą one być odczytywane tylko przez jedną stronę interfejsu HIDL. Jeśli na przykład kod vendor.img przekazuje komponentowi system.img ciąg znaków lub dane vec<uint8_t>, system.img nie może go przeanalizować. Może je przekazać tylko do interpretacji komponentowi vendor.img. Podczas przekazywania wartości z vendor.img do kodu dostawcy na urządzeniu system.img lub na inne urządzenie należy dokładnie opisać format danych i sposób ich interpretacji. Dane te nadal stanowią część interfejsu.

Wskazówki

Implementacja lub klient HAL powinien być tworzony tylko z wykorzystaniem plików .hal (czyli nie trzeba sprawdzać kodu źródłowego Androida ani publicznych standardów). Zalecamy określenie dokładnego wymaganego zachowania. Sformułowania takie jak „implementacja może wykonać A lub B” zachęcają do tego, aby implementacje były powiązane z klientami, dla których są tworzone.

Układ kodu HIDL

HIDL obejmuje pakiety podstawowe i pakiety dostawców.

Interfejsy podstawowe HIDL to te określone przez Google. Nazwa pakietu, do którego należą, zaczyna się od android.hardware. i jest nazwana według nazwy podsystemu. Może mieć wbudowane poziomy nazewnictwa. Na przykład pakiet NFC ma nazwę android.hardware.nfc, a pakiet aparatu – android.hardware.camera. Zasadniczo pakiet podstawowy ma nazwę android.hardware.[name1].[name2]…. Pakiety HIDL mają oprócz nazwy również wersję. Na przykład pakiet android.hardware.camera może mieć wersję 3.4. Jest to ważne, ponieważ wersja pakietu wpływa na jego położenie w drzewie źródłowym.

Wszystkie podstawowe pakiety są umieszczane w systemie kompilacji w folderze hardware/interfaces/. Pakiet android.hardware.[name1].[name2]… w wersji $m.$n znajduje się w katalogu hardware/interfaces/name1/name2//$m.$n/; pakiet android.hardware.camera w wersji 3.4 znajduje się w katalogu hardware/interfaces/camera/3.4/.. Istnieje zakodowane na stałe mapowanie między prefiksem pakietu android.hardware. a ścieżką hardware/interfaces/.

Pakiety niebędące kluczowymi (dostawcy) to pakiety produkowane przez dostawcę SoC lub ODM. Prefiks pakietów niebędących pakietami podstawowymi to vendor.$(VENDOR).hardware., gdzie $(VENDOR) oznacza dostawcę SoC lub OEM/ODM. Jest ona mapowana na ścieżkę vendor/$(VENDOR)/interfaces w drzewie (to mapowanie jest również zakodowane na stałe).

Pełne i jednoznaczne nazwy typów zdefiniowanych przez użytkownika

W HIDL każda UDT ma pełną nazwę, która składa się z nazwy UDT, nazwy pakietu, w którym jest zdefiniowana UDT, oraz wersji pakietu. Pełna nazwa kwalifikowana jest używana tylko wtedy, gdy deklarowane są wystąpienia typu, a nie tam, gdzie definiuje się sam typ. Załóżmy na przykład, że pakiet android.hardware.nfc, w wersji 1.0 definiuje strukturę o nazwie NfcData. W miejscu deklaracji (czy to w types.hal, czy w deklaracji interfejsu) należy po prostu podać:

struct NfcData {
    vec<uint8_t> data;
};

Podczas deklarowania wystąpienia tego typu (w ramach struktury danych lub jako parametr metody) użyj pełnej nazwy typu:

android.hardware.nfc@1.0::NfcData

Ogólna składnia to: PACKAGE@VERSION::UDT, gdzie:

  • PACKAGE to nazwa oddzielona kropkami pakietu HIDL (np. android.hardware.nfc).
  • VERSION to format główny.podrzędna-wersja pakietu (np. 1.0).
  • UDT to nazwa rozdzielona kropkami typu danych UDT HIDL. HIDL obsługuje zagnieżdżone typy danych użytkownika, a interfejsy HIDL mogą zawierać typy danych użytkownika (jako zagnieżdżone deklaracje), więc do uzyskiwania dostępu do nazw używa się kropek.

Jeśli na przykład w pliku common.types w pakiecie android.hardware.example w wersji 1.0 zdefiniowano następującą deklarację zagnieżdżoną:

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

Pełna i jednoznaczna nazwa Bar to android.hardware.example@1.0::Foo.Bar. Jeśli deklaracja zagnieżdżoną znajdowała się nie tylko w powyższym pakiecie, ale też w interfejsie o nazwie IQuux:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

Pełna i jednoznaczna nazwa Bar to android.hardware.example@1.0::IQuux.Foo.Bar.

W obu przypadkach Bar może być używane jako Bar tylko w zakresie deklaracji Foo. Na poziomie pakietu lub interfejsu musisz odwoływać się do Bar za pomocą Foo: Foo.Bar, tak jak w deklaracji metody doSomething powyżej. Możesz też zadeklarować metodę w bardziej rozbudowany sposób:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

Pełne wartości wyliczenia

Jeśli typ niestandardowy jest typem wyliczeniowym, każda wartość typu wyliczeniowego ma pełną nazwę, która zaczyna się od pełnej nazwy typu wyliczeniowego, a następnie dwukropka i nazwy wartości typu wyliczeniowego. Załóżmy na przykład, że pakiet android.hardware.nfc, w wersji 1.0 definiuje typ enumeracji NfcStatus:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Pełna i jednoznaczna nazwa STATUS_OK to:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

Ogólna składnia to: PACKAGE@VERSION::UDT:VALUE, gdzie:

  • PACKAGE@VERSION::UDT to dokładnie ta sama pełna nazwa typu enum.
  • VALUE to nazwa wartości.

Reguły automatycznego wnioskowania

Nie trzeba podawać pełnej nazwy typu danych użytkownika. W nazwie UDT można bezpiecznie pominąć te elementy:

  • Pakiet, np. @1.0::IFoo.Type
  • Zarówno pakiet, jak i wersja, np. IFoo.Type

HIDL próbuje uzupełnić nazwę za pomocą reguł automatycznego zakłócenia (niższa liczba reguły oznacza wyższy priorytet).

Reguła 1

Jeśli nie podano pakietu ani wersji, próba wyszukania nazwy lokalnej kończy się niepowodzeniem. Przykład:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

NfcErrorMessage jest wyszukiwany lokalnie, a typedef jest znajdowany powyżej. Wartość NfcData jest też wyszukiwana lokalnie, ale ponieważ nie jest zdefiniowana lokalnie, używane są reguły 2 i 3. @1.0::NfcStatus pozwala na wybór wersji, więc zasada 1 nie ma zastosowania.

Reguła 2

Jeśli reguła 1 zawiedzie i w nazwie kwalifikowanej brakuje komponentu (pakiet, wersja lub pakiet i wersja), komponent zostanie automatycznie uzupełniony informacjami z bieżącego pakietu. Kompilator HIDL szuka wtedy w bieżącym pliku (oraz we wszystkich importach) automatycznie wypełnionej pełnej nazwy. W przypadku przykładu powyżej załóżmy, że deklaracja ExtendedNfcData została złożona w tym samym pakiecie (android.hardware.nfc) w tej samej wersji (1.0) co NfcData, jak pokazano poniżej:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

Kompilator HIDL wypełnia nazwę pakietu i nazwę wersji z bieżącego pakietu, aby utworzyć pełną nazwę typu danych HIDL: android.hardware.nfc@1.0::NfcData. Nazwa występuje w bieżącym pakiecie (zakładając, że został on prawidłowo zaimportowany), więc jest używana w deklaracji.

Nazwa w bieżącym pakiecie jest importowana tylko wtedy, gdy spełniony jest jeden z tych warunków:

  • Jest ona importowana w wyraźny sposób za pomocą instrukcji import.
  • Jest zdefiniowany w pliku types.hal w bieżącym pakiecie

Ten sam proces jest stosowany, jeśli NfcData został sklasyfikowany tylko na podstawie numeru wersji:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

Reguła 3

Jeśli reguła 2 nie znajdzie dopasowania (typ danych UDT nie jest zdefiniowany w bieżącym pakiecie), kompilator HIDL przeszukuje wszystkie zaimportowane pakiety w celu znalezienia dopasowania. W przypadku powyższego przykładu załóżmy, że ExtendedNfcData jest zadeklarowany w wersji 1.1 pakietu android.hardware.nfc, 1.1 importuje 1.0 zgodnie z oczekiwaniami (patrz Rozszerzenia na poziomie pakietu), a definicja określa tylko nazwę typu danych użytkownika:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

Kompilator szuka dowolnego typu danych użytkownika o nazwie NfcData i znajduje go w android.hardware.nfc w wersji 1.0, co powoduje pełną nazwę typu danych użytkownika android.hardware.nfc@1.0::NfcData. Jeśli dla danego częściowo kwalifikowanego typu UDT zostanie znalezione więcej niż jedno dopasowanie, kompilator HIDL zwróci błąd.

Przykład

Zgodnie z regułą 2 typ zaimportowany z bieżącego pakietu jest preferowany przed typem zaimportowanym z innego pakietu:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S jest interpolowana jako android.hardware.bar@1.0::S i znajduje się w bar/1.0/types.hal (ponieważ types.hal jest automatycznie importowana).
  • IFooCallback jest interpolowana jako android.hardware.bar@1.0::IFooCallback zgodnie z regułą 2, ale nie można jej znaleźć, ponieważ bar/1.0/IFooCallback.hal nie jest importowana automatycznie (jak types.hal). Dlatego reguła 3 zwraca zamiast tego wartość android.hardware.foo@1.0::IFooCallback, która jest importowana za pomocą kolumny import android.hardware.foo@1.0;.

types.hal

Każdy pakiet HIDL zawiera plik types.hal zawierający UDT, które są udostępniane wszystkim interfejsom w tym pakiecie. Typy HIDL są zawsze publiczne. Niezależnie od tego, czy typ niestandardowy jest zadeklarowany w types.hal czy w deklaracji interfejsu, jest on dostępny poza zakresem, w którym został zdefiniowany. types.halnie ma na celu opisywania publicznego interfejsu API pakietu, ale hostowania typów danych UDT używanych przez wszystkie interfejsy w pakiecie. Ze względu na charakter HIDL wszystkie UDT są częścią interfejsu.

types.hal składa się z typów danych nieustrukturowanych i wykazów import. Funkcja types.hal jest dostępna dla każdego interfejsu pakietu (jest to domyślny import), więc te instrukcje import są z definicji dostępne na poziomie pakietu. UDT-y w types.hal mogą też zawierać zaimportowane UDT-y i interfejsy.

Na przykład w przypadku IFoo.hal:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

Importowane są te elementy:

  • android.hidl.base@1.0::IBase (domyślnie)
  • android.hardware.foo@1.0::types (domyślnie)
  • Wszystko w android.hardware.bar@1.0 (w tym wszystkie interfejsy i ich types.hal)
  • types.halandroid.hardware.baz@1.0::types (interfejsy w android.hardware.baz@1.0 nie są importowane)
  • IQux.haltypes.halandroid.hardware.qux@1.0
  • Quuzandroid.hardware.quuz@1.0 (zakładając, że Quuz jest zdefiniowana w types.hal, cały plik types.hal jest analizowany, ale typy inne niż Quuz nie są importowane).

Wersje na poziomie interfejsu

Każdy interfejs w pakiecie znajduje się w osobnym pliku. Pakiet, do którego należy interfejs, jest deklarowany u góry interfejsu za pomocą instrukcji package. Zgodnie z deklaracją pakietu może być podany zero lub więcej importów na poziomie interfejsu (częściowych lub pełnych pakietów). Przykład:

package android.hardware.nfc@1.0;

W HIDL interfejsy mogą dziedziczyć po innych interfejsach za pomocą słowa kluczowego extends. Aby interfejs mógł rozszerzać inny interfejs, musi mieć do niego dostęp za pomocą instrukcji import. Nazwa rozszerzanego interfejsu (interfejs podstawowy) podlega regułom kwalifikacji nazwy typu opisanym powyżej. Interfejs może dziedziczyć tylko z jednego interfejsu. HIDL nie obsługuje wielokrotnego dziedziczenia.

Przykłady wersji uprev poniżej używają tego pakietu:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

Reguły Uprev

Aby zdefiniować pakiet package@major.minor, musi być spełniony warunek A lub wszystkie warunki B:

Reguła A „Czy jest to pierwsza wersja podrzędna”: wszystkie poprzednie wersje podrzędne (package@major.0, package@major.1, …, package@major.(minor-1)) nie mogą być zdefiniowane.
LUB
Reguła B

Wszystkie te stwierdzenia są prawdziwe:

  1. „Poprzednia wersja mniejsza jest ważna”: package@major.(minor-1)musi być zdefiniowany i stosować się do tej samej reguły A (żadne z package@major.0package@major.(minor-2) nie są zdefiniowane) lub reguły B (jeśli jest to nowsza wersja @major.(minor-2));

    I

  2. „Dziedzi co najmniej 1 interfejs o tej samej nazwie”: istnieje interfejs package@major.minor::IFoo, który rozszerza interfejs package@major.(minor-1)::IFoo (jeśli poprzedni pakiet ma interfejs);

    I

  3. „Brak dziedziczonego interfejsu o innej nazwie”: nie może istnieć package@major.minor::IBar, który rozszerza package@major.(minor-1)::IBaz, gdzie IBarIBaz to 2 różne nazwy. Jeśli istnieje interfejs o tej samej nazwie, package@major.minor::IBar musi rozszerzać package@major.(minor-k)::IBar w taki sposób, aby nie istniała żadna klasa IBar z mniejszym k.

Ze względu na regułę A:

  • Pakiet może zaczynać się od dowolnej wersji podrzędnej (na przykład android.hardware.biometrics.fingerprint zaczyna się od @2.1).
  • Wymaganie „android.hardware.foo@1.0 nie jest zdefiniowane” oznacza, że katalog hardware/interfaces/foo/1.0 nie powinien w ogóle istnieć.

Jednak reguła A nie ma wpływu na pakiet o tej samej nazwie, ale innej głównej wersji (na przykład android.hardware.camera.device ma zdefiniowane zarówno @1.0, jak i @3.2; @3.2 nie musi wchodzić w interakcje z @1.0). Dlatego @3.2::IExtFoo może rozszerzać @1.0::IFoo.

Jeśli nazwa pakietu jest inna, package@major.minor::IBar może rozszerzać interfejs o inną nazwę (na przykład android.hardware.bar@1.0::IBar może rozszerzać android.hardware.baz@2.2::IBaz). Jeśli interfejs nie deklaruje wprost supertypu za pomocą słowa kluczowego extend, rozszerza android.hidl.base@1.0::IBase (z wyjątkiem IBase).

Wymagania B.2 i B.3 muszą być spełnione jednocześnie. Na przykład nawet jeśli android.hardware.foo@1.1::IFoo rozszerza android.hardware.foo@1.0::IFoo, aby spełnić regułę B.2, a android.hardware.foo@1.1::IExtBar rozszerza android.hardware.foo@1.0::IBar, nadal nie jest to prawidłowa aktualizacja.

Interfejsy Uprev

Aby zaktualizować wersję android.hardware.example@1.0 (zdefiniowaną powyżej) do wersji @1.1:

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

To jest import na poziomie pakietu w wersji 1.0 w komponencie android.hardware.example w wersji types.hal. Chociaż w wersji 1.1 pakietu nie dodano nowych UDT, nadal potrzebne są odwołania do UDT w wersji 1.0, dlatego importowanie na poziomie pakietu jest konieczne w wersji types.hal. (Tego samego efektu można było osiągnąć, importując plik na poziomie interfejsu w pliku IQuux.hal).

W deklaracji extends @1.0::IQuux (IQuux) określono wersję IQuux, która jest dziedziczona (wymagane jest rozróżnienie, ponieważ IQuux służy do deklarowania interfejsu i dziedziczenia z interfejsu). Deklaracje to po prostu nazwy, które dziedziczą wszystkie atrybuty pakietu i wersji w miejscu deklaracji, więc rozróżnienie musi być zawarte w nazwie interfejsu podstawowego. Moglibyśmy też użyć w pełni kwalifikowanej nazwy typu danych użytkownika, ale byłoby to zbędne.

Nowy interfejs IQuux nie deklaruje ponownie metody fromFooToBar(), którą dziedziczy z @1.0::IQuux. Wystarczy, że wymieni nową metodę, którą dodaje fromBarToFoo(). W HIDL dziedziczone metody nie mogą być ponownie deklarowane w interfejsach podrzędnych, więc interfejs IQuux nie może jawnie deklarować metody fromFooToBar().

Konwencje Uprev

Czasami nazwy interfejsów muszą zostać zmienione w rozszerzonym interfejsie. Zalecamy, aby rozszerzenia, struktury i zbiory miały taką samą nazwę jak typ, z którego pochodzą, chyba że różnią się na tyle, że uzasadnia to nadanie im nowej nazwy. Przykłady:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

Jeśli metoda może mieć nową nazwę semakntyczną (np. fooWithLocation), jest to preferowane. W przeciwnym razie powinna mieć nazwę podobną do nazwy rozszerzenia. Na przykład metoda foo_1_1 w pliku @1.1::IFoo może zastąpić funkcjonalność metody foo w pliku @1.0::IFoo, jeśli nie ma lepszej nazwy alternatywnej.

Wersje na poziomie pakietu

Wersje HIDL są tworzone na poziomie pakietu. Po opublikowaniu pakietu nie można go zmienić (nie można zmienić jego zestawu interfejsów ani typów danych UDT). Pakiety mogą być powiązane ze sobą na kilka sposobów, które można wyrazić za pomocą kombinacji dziedziczenia na poziomie interfejsu i tworzenia typów danych użytkownika za pomocą kompozycji.

Jeden typ relacji jest jednak ściśle zdefiniowany i musi być stosowany: dziedziczenie wstecz na poziomie pakietu. W tym scenariuszu pakiet nadrzędny to pakiet, z którego dziedziczy się, a pakiet podrzędny to ten, który rozszerza pakiet nadrzędny. Reguły dziedziczenia zgodne wstecznie na poziomie pakietu:

  1. Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone przez interfejsy w pakiecie podrzędnym.
  2. Do nowego pakietu można też dodawać nowe interfejsy (bez ograniczeń dotyczących relacji z innymi interfejsami w innych pakietach).
  3. Możesz też dodawać nowe typy danych, aby używać ich w ramach nowych metod istniejących interfejsów lub nowych interfejsów.

Te reguły można zaimplementować za pomocą dziedziczenia na poziomie interfejsu HIDL i kompozycji UDT, ale wymagają znajomości tych relacji na poziomie meta, aby wiedzieć, że stanowią one kompatybilne wstecz rozszerzenie pakietu. Ta wiedza jest wywnioskowana w ten sposób:

Jeśli pakiet spełnia ten wymóg, hidl-gen stosuje reguły zgodności wstecznej.