Obsługa wersji

HIDL wymaga, aby każdy interfejs napisany w języku HIDL był wersjonowany. Po opublikowaniu interfejsu HAL jest on blokowany i wszelkie dalsze zmiany należy wprowadzić w nowej wersji tego interfejsu. Choć danego opublikowanego interfejsu nie można modyfikować, można go rozbudować o inny interfejs.

Struktura kodu HIDL

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

  • Typy zdefiniowane przez użytkownika (UDT) . HIDL zapewnia dostęp do zestawu prymitywnych typów danych, których można używać do tworzenia bardziej złożonych typów za pomocą struktur, unii i wyliczeń. UDT są przekazywane do metod interfejsów i mogą być definiowane na poziomie pakietu (wspólnym dla wszystkich interfejsów) lub lokalnie w interfejsie.
  • Interfejsy . Jako podstawowy element składowy HIDL, interfejs składa się z deklaracji UDT i metod. Interfejsy mogą również dziedziczyć z innego interfejsu.
  • Pakiety . Organizuje powiązane interfejsy HIDL i typy danych, na których działają. Pakiet jest identyfikowany przez nazwę i wersję i zawiera następujące elementy:
    • Plik definicji typu danych o nazwie types.hal .
    • Zero lub więcej interfejsów, każdy w osobnym pliku .hal .

Plik definicji typu danych types.hal zawiera tylko UDT (wszystkie UDT na poziomie pakietu są przechowywane w jednym pliku). Reprezentacje w języku docelowym są dostępne dla wszystkich interfejsów w pakiecie.

Filozofia wersjonowania

Pakiet HIDL (taki jak android.hardware.nfc ), po opublikowaniu dla danej wersji (np. 1.0 ), jest niezmienny; nie da się tego zmienić. Modyfikacje interfejsów w pakiecie lub jakiekolwiek zmiany w jego UDT mogą nastąpić tylko w innym pakiecie.

W HIDL wersjonowanie ma zastosowanie na poziomie pakietu, a nie na poziomie interfejsu, a wszystkie interfejsy i UDT w pakiecie mają tę samą wersję. Wersje pakietów są zgodne z wersjami semantycznymi bez poziomu poprawki i komponentów metadanych kompilacji. W obrębie danego pakietu pomniejsza zmiana wersji oznacza, że ​​nowa wersja pakietu jest wstecznie kompatybilna ze starym pakietem, a większa zmiana wersji oznacza, że ​​nowa wersja pakietu nie jest wstecznie kompatybilna ze starym pakietem.

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

  • Zupełnie nie .
  • Rozszerzalność wsteczna na poziomie pakietu . Dzieje się tak w przypadku nowych aktualizacji mniejszych wersji (następnej zwiększonej wersji) pakietu; nowy pakiet ma tę samą nazwę i wersję główną co stary pakiet, ale jest wyższą wersją pomocniczą. Funkcjonalnie nowy pakiet jest rozszerzeniem starego pakietu, co oznacza:
    • Interfejsy najwyższego poziomu pakietu nadrzędnego są obecne w nowym pakiecie, chociaż interfejsy mogą mieć nowe metody, nowe UDT lokalne dla interfejsu (rozszerzenie poziomu interfejsu opisane poniżej) i nowe UDT w types.hal .
    • Do nowego pakietu mogą zostać dodane także nowe interfejsy.
    • Wszystkie typy danych pakietu nadrzędnego są obecne w nowym pakiecie i mogą być obsługiwane przez (ewentualnie ponownie zaimplementowane) metody ze starego pakietu.
    • Można także dodać nowe typy danych do wykorzystania w nowych metodach udoskonalonych istniejących interfejsów lub w nowych interfejsach.
  • Rozszerzalność wstecznie kompatybilna na poziomie interfejsu . Nowy pakiet może także rozszerzyć pakiet oryginalny, składając się z logicznie oddzielnych interfejsów, które po prostu zapewniają dodatkową funkcjonalność, a nie podstawową. W tym celu pożądane mogą być:
    • Interfejsy w nowym pakiecie muszą korzystać z typów danych starego pakietu.
    • Interfejsy w nowym pakiecie mogą rozszerzać interfejsy jednego lub większej liczby starych pakietów.
  • Rozszerz oryginalną niezgodność wsteczną . Jest to główna wersja pakietu i nie musi być między nimi żadnej korelacji. Jeśli istnieje, można to wyrazić za pomocą kombinacji typów ze starszej wersji pakietu i dziedziczenia podzbioru interfejsów ze starego pakietu.

Strukturyzacja interfejsów

W przypadku dobrze zorganizowanego interfejsu dodanie nowych typów funkcjonalności, które nie są częścią oryginalnego projektu, powinno wymagać modyfikacji interfejsu HIDL. I odwrotnie, jeśli możesz lub spodziewasz się wprowadzenia zmian po obu stronach interfejsu, wprowadzających nową funkcjonalność bez zmiany samego interfejsu, wówczas interfejs nie jest ustrukturyzowany.

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

Wymagania

Wszystkie dane przesyłane przez HIDL muszą być jawnie zdefiniowane. Aby zapewnić, że wdrożenie i klient będą mogli nadal współpracować, nawet jeśli zostaną skompilowane osobno lub opracowane niezależnie, dane muszą spełniać następujące wymagania:

  • Można go opisać bezpośrednio w języku HIDL (za pomocą wyliczeń struktur itp.) Z nazwami semantycznymi i znaczeniem.
  • Można opisać za pomocą normy publicznej, takiej jak ISO/IEC 7816.
  • Można opisać za pomocą standardu sprzętowego lub fizycznego układu sprzętu.
  • W razie potrzeby mogą to być nieprzezroczyste dane (takie jak klucze publiczne, identyfikatory itp.).

Jeśli używane są nieprzezroczyste dane, należy je odczytać tylko z jednej strony interfejsu HIDL. Na przykład, jeśli kod vendor.img przekazuje komponentowi w pliku system.img komunikat w postaci ciągu znaków lub dane vec<uint8_t> , dane te nie mogą zostać przeanalizowane przez sam system.img ; można go jedynie przekazać z powrotem do vendor.img w celu interpretacji. Podczas przekazywania wartości z vendor.img do kodu dostawcy w system.img lub do innego urządzenia, format danych i sposób ich interpretacji muszą być dokładnie opisane i nadal stanowią część interfejsu .

Wytyczne

Powinieneś być w stanie napisać implementację lub klienta HAL, używając tylko plików .hal (tzn. nie powinieneś szukać źródła Androida ani publicznych standardów). Zalecamy określenie dokładnego wymaganego zachowania. Stwierdzenia takie jak „wdrożenie może zrobić A lub B” zachęcają, aby wdrożenia połączyły się z klientami, z którymi są opracowywane.

Układ kodu HIDL

HIDL obejmuje pakiety podstawowe i dostawcy.

Podstawowe interfejsy HIDL to te określone przez Google. Pakiety, do których należą, zaczynają się od android.hardware. i są nazywane według podsystemu, potencjalnie z zagnieżdżonymi poziomami nazewnictwa. Na przykład pakiet NFC ma nazwę android.hardware.nfc , a pakiet aparatu to android.hardware.camera . Ogólnie rzecz biorąc, pakiet podstawowy ma nazwę android.hardware. [ name1 ].[ name2 ]…. Pakiety HIDL oprócz nazwy mają wersję. Na przykład pakiet android.hardware.camera może być w wersji 3.4 ; jest to ważne, ponieważ wersja pakietu wpływa na jego umiejscowienie w drzewie źródłowym.

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

Pakiety inne niż podstawowe (dostawcy) to pakiety produkowane przez dostawcę SoC lub ODM. Przedrostek pakietów innych niż podstawowe to vendor.$(VENDOR).hardware. gdzie $(VENDOR) odnosi się do dostawcy SoC lub OEM/ODM. Odwzorowuje to ścieżkę vendor/$(VENDOR)/interfaces w drzewie (to mapowanie jest również zakodowane na stałe).

W pełni kwalifikowane nazwy typów zdefiniowanych przez użytkownika

W języku HIDL każdy UDT ma w pełni kwalifikowaną nazwę, która składa się z nazwy UDT, nazwy pakietu, w którym zdefiniowano UDT, oraz wersji pakietu. W pełni kwalifikowana nazwa jest używana tylko wtedy, gdy deklarowane są instancje typu, a nie wtedy, gdy zdefiniowany jest sam typ. Załóżmy na przykład, że pakiet android.hardware.nfc, w wersji 1.0 definiuje strukturę o nazwie NfcData . Na stronie deklaracji (czy to w types.hal , czy w deklaracji interfejsu) deklaracja po prostu stwierdza:

struct NfcData {
    vec<uint8_t> data;
};

Deklarując instancję tego typu (czy to w strukturze danych, czy jako parametr metody), użyj w pełni kwalifikowanej nazwy typu:

android.hardware.nfc@1.0::NfcData

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

  • PACKAGE to oddzielona kropkami nazwa pakietu HIDL (np. android.hardware.nfc ).
  • VERSION jest oddzielonym kropkami formatem wersji głównej.podrzędnej pakietu (np. 1.0 ).
  • UDT to oddzielona kropkami nazwa UDT HIDL. Ponieważ HIDL obsługuje zagnieżdżone UDT, a interfejsy HIDL mogą zawierać UDT (rodzaj zagnieżdżonej deklaracji), w celu uzyskania dostępu do nazw używane są kropki.

Na przykład, jeśli w pliku typów wspólnych w pakiecie android.hardware.example wersja 1.0 zdefiniowano następującą deklarację zagnieżdżoną:

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

W pełni kwalifikowana nazwa Bar to android.hardware.example@1.0::Foo.Bar . Jeśli oprócz tego, że znajdowała się w powyższym pakiecie, zagnieżdżona deklaracja znajdowała się 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);
};

W pełni kwalifikowana nazwa Bar to android.hardware.example@1.0::IQuux.Foo.Bar .

W obu przypadkach Bar można nazwać Bar jedynie w zakresie deklaracji Foo . Na poziomie pakietu lub interfejsu musisz odwołać się do Bar poprzez Foo : Foo.Bar , jak w deklaracji metody doSomething powyżej. Alternatywnie możesz zadeklarować metodę bardziej szczegółowo jako:

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

W pełni kwalifikowane wartości wyliczenia

Jeśli UDT jest typem wyliczeniowym, każda wartość typu wyliczeniowego ma w pełni kwalifikowaną nazwę rozpoczynającą się od w pełni kwalifikowanej nazwy typu wyliczeniowego, po której następuje dwukropek, a następnie nazwa wartości wyliczeniowej. Załóżmy na przykład, że pakiet android.hardware.nfc, w wersji 1.0 definiuje typ wyliczeniowy NfcStatus :

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

Odnosząc się do STATUS_OK , w pełni kwalifikowana nazwa 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, w pełni kwalifikowana nazwa typu wyliczeniowego.
  • VALUE to nazwa wartości.

Reguły autownioskowania

Nie trzeba podawać pełnej nazwy UDT. Nazwa UDT może bezpiecznie pominąć następujące elementy:

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

HIDL próbuje uzupełnić nazwę, korzystając z reguł automatycznej interferencji (niższy numer reguły oznacza wyższy priorytet).

Zasada nr 1

Jeśli nie podano żadnego pakietu ani wersji, zostanie podjęta próba wyszukiwania nazw lokalnych. Przykład:

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

NfcErrorMessage jest wyszukiwany lokalnie i znajdowany jest znajdujący się powyżej typedef . NfcData jest również wyszukiwana lokalnie, ale ponieważ nie jest zdefiniowana lokalnie, stosowane są reguły 2 i 3. @1.0::NfcStatus zapewnia wersję, więc zasada 1 nie ma zastosowania.

Zasada 2

Jeśli reguła 1 zawiedzie i brakuje komponentu w pełni kwalifikowanej nazwy (pakietu, wersji lub pakietu i wersji), komponent zostanie automatycznie uzupełniony informacjami z bieżącego pakietu. Kompilator HIDL następnie przegląda bieżący plik (i wszystkie importy), aby znaleźć automatycznie uzupełnioną, w pełni kwalifikowaną nazwę. Korzystając z powyższego przykładu, załóżmy, że deklaracja ExtendedNfcData została dokonana w tym samym pakiecie ( android.hardware.nfc ) w tej samej wersji ( 1.0 ) co NfcData w następujący sposób:

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

Kompilator HIDL wypełnia nazwę pakietu i nazwę wersji z bieżącego pakietu, aby wygenerować w pełni kwalifikowaną nazwę UDT android.hardware.nfc@1.0::NfcData . Ponieważ nazwa istnieje w bieżącym pakiecie (zakładając, że została poprawnie zaimportowana), zostanie użyta w deklaracji.

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

  • Jest importowany jawnie za pomocą instrukcji import .
  • Jest zdefiniowany w types.hal w bieżącym pakiecie

Ten sam proces jest wykonywany, jeśli NfcData zostało zakwalifikowane jedynie na podstawie numeru wersji:

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

Zasada 3

Jeśli reguła 2 nie znajdzie dopasowania (w bieżącym pakiecie nie zdefiniowano UDT), kompilator HIDL szuka dopasowania we wszystkich zaimportowanych pakietach. Korzystając z powyższego przykładu, załóżmy, że ExtendedNfcData jest zadeklarowany w wersji 1.1 pakietu android.hardware.nfc , 1.1 importuje 1.0 tak jak powinno (zobacz Rozszerzenia na poziomie pakietu ), a definicja określa tylko nazwę UDT:

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

Kompilator szuka dowolnego UDT o nazwie NfcData i znajduje go w android.hardware.nfc w wersji 1.0 , co skutkuje w pełni kwalifikowanym UDT o android.hardware.nfc@1.0::NfcData . Jeśli dla danego częściowo kwalifikowanego UDT zostanie znalezione więcej niż jedno dopasowanie, kompilator HIDL zgłosi błąd.

Przykład

Stosując regułę 2, importowany typ zdefiniowany w bieżącym pakiecie jest preferowany w stosunku do typu importowanego 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 interpolowany jako android.hardware.bar@1.0::S i znajduje się w bar/1.0/types.hal (ponieważ types.hal jest importowany automatycznie).
  • IFooCallback jest interpolowany jako android.hardware.bar@1.0::IFooCallback przy użyciu reguły 2, ale nie można go znaleźć, ponieważ bar/1.0/IFooCallback.hal nie jest importowany automatycznie (tak jak jest to types.hal ). Zatem reguła 3 rozwiązuje go zamiast tego do android.hardware.foo@1.0::IFooCallback , który jest importowany poprzez import android.hardware.foo@1.0; ).

typy.hal

Każdy pakiet HIDL zawiera plik types.hal zawierający UDT, które są współdzielone pomiędzy wszystkimi interfejsami uczestniczącymi w tym pakiecie. Typy HIDL są zawsze publiczne; niezależnie od tego, czy UDT jest zadeklarowany w types.hal , czy w deklaracji interfejsu, typy te są dostępne poza zakresem, w którym są zdefiniowane. types.hal nie ma na celu opisywania publicznego API pakietu, ale raczej hostuje UDT używane przez wszystkie interfejsy w pakiecie. Ze względu na naturę HIDL wszystkie UDT są częścią interfejsu.

types.hal składa się z UDT i instrukcji import . Ponieważ types.hal jest dostępny dla każdego interfejsu pakietu (jest to import niejawny), te instrukcje import z definicji dotyczą poziomu pakietu. UDT w types.hal mogą również zawierać zaimportowane w ten sposób UDT i interfejsy.

Na przykład dla 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ą:

  • 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.hal z android.hardware.baz@1.0::types (interfejsy w android.hardware.baz@1.0 nie są importowane)
  • IQux.hal i types.hal z android.hardware.qux@1.0
  • Quuz z android.hardware.quuz@1.0 (zakładając, że Quuz jest zdefiniowany w types.hal , analizowany jest cały plik types.hal , ale typy inne niż Quuz nie są importowane).

Wersjonowanie na poziomie interfejsu

Każdy interfejs w pakiecie znajduje się w swoim własnym pliku. Pakiet, do którego należy interfejs, jest deklarowany na górze interfejsu za pomocą instrukcji package . Po deklaracji pakietu może zostać wyszczególniony zero lub więcej importów na poziomie interfejsu (częściowych lub całych pakietów). Na przykład:

package android.hardware.nfc@1.0;

W języku HIDL interfejsy mogą dziedziczyć z innych interfejsów za pomocą słowa kluczowego extends . Aby interfejs mógł rozszerzać inny interfejs, musi mieć do niego dostęp poprzez instrukcję import . Nazwa rozszerzanego interfejsu (interfejs podstawowy) jest zgodna z opisanymi powyżej zasadami kwalifikacji nazwy typu. Interfejs może dziedziczyć tylko z jednego interfejsu; HIDL nie obsługuje dziedziczenia wielokrotnego.

Poniższe przykłady wersji uprev korzystają z następującego 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);
}

Zasady uprew

Aby zdefiniować pakiet package@major.minor , albo A, albo wszystkie B muszą być prawdziwe:

Zasada A „To początkowa wersja dodatkowa”: Nie można zdefiniować wszystkich poprzednich wersji pomocniczych, package@major.0 , package@major.1 , …, package@major.(minor-1) .
LUB
Zasada B

Wszystkie poniższe stwierdzenia są prawdziwe:

  1. „Poprzednia wersja pomocnicza jest prawidłowa”: należy zdefiniować package@major.(minor-1) i przestrzegać tej samej reguły A (żadna z package@major.0 do package@major.(minor-2) nie jest zdefiniowana) lub reguły B (jeśli jest to zmiana z @major.(minor-2) );

    I

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

    I

  3. „Brak odziedziczonego interfejsu o innej nazwie”: Nie może istnieć package@major.minor::IBar rozszerzający package@major.(minor-1)::IBaz , gdzie IBar i IBaz to dwie różne nazwy. Jeśli istnieje interfejs o tej samej nazwie, package@major.minor::IBar musi rozszerzać package@major.(minor-k)::IBar tak, aby nie istniał żaden IBar z mniejszym k.

Ze względu na zasadę A:

  • Pakiet może zaczynać się od dowolnego mniejszego numeru wersji (na przykład android.hardware.biometrics.fingerprint zaczyna się od @2.1 ).
  • Wymóg „ android.hardware.foo@1.0 nie jest zdefiniowany” oznacza, że ​​katalog hardware/interfaces/foo/1.0 w ogóle nie powinien istnieć.

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

Pod warunkiem, że nazwa pakietu jest inna, package@major.minor::IBar może rozszerzać się z interfejsu o innej nazwie (na przykład android.hardware.bar@1.0::IBar może rozszerzać android.hardware.baz@2.2::IBaz ). Jeśli interfejs nie zadeklaruje jawnie nadtypu za pomocą słowa kluczowego extend , rozszerzy android.hidl.base@1.0::IBase (z wyjątkiem samego IBase ).

Należy jednocześnie przestrzegać B.2 i B.3. Na przykład, nawet jeśli android.hardware.foo@1.1::IFoo rozszerza android.hardware.foo@1.0::IFoo , aby przekazać regułę B.2, jeśli android.hardware.foo@1.1::IExtBar rozszerza android.hardware.foo@1.0::IBar , to nadal nie jest poprawna wersja.

Udoskonalanie interfejsów

Aby zaktualizować android.hardware.example@1.0 (zdefiniowany powyżej) do @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 wersji 1.0 pliku android.hardware.example na poziomie pakietu do types.hal . Chociaż w wersji 1.1 pakietu nie dodano żadnych nowych UDT, odniesienia do UDT w wersji 1.0 są nadal potrzebne, stąd import na poziomie pakietu w types.hal . (Ten sam efekt można było osiągnąć poprzez import na poziomie interfejsu w IQuux.hal .)

W extends @1.0::IQuux w deklaracji IQuux określiliśmy wersję IQuux , która jest dziedziczona (wymagane jest ujednoznacznienie, ponieważ IQuux służy do deklarowania interfejsu i dziedziczenia z interfejsu). Ponieważ deklaracje są po prostu nazwami, które dziedziczą wszystkie atrybuty pakietu i wersji w miejscu deklaracji, ujednoznacznienie musi dotyczyć nazwy interfejsu podstawowego; moglibyśmy także skorzystać z w pełni wykwalifikowanego UDT, ale byłoby to zbędne.

Nowy interfejs IQuux nie deklaruje ponownie metody fromFooToBar() dziedziczy ona z @1.0::IQuux ; po prostu wyświetla listę nowej metody, którą dodaje fromBarToFoo() . W języku HIDL odziedziczone metody nie mogą być ponownie zadeklarowane w interfejsach potomnych, więc interfejs IQuux nie może jawnie zadeklarować metody fromFooToBar() .

Konwencje uprev

Czasami nazwy interfejsów muszą zmieniać nazwę interfejsu rozszerzającego. Zalecamy, aby rozszerzenia, struktury i związki wyliczeniowe miały tę samą nazwę, co rozszerzenie, chyba że różnią się na tyle, że uzasadniają nową nazwę. 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ę semantyczną (na przykład fooWithLocation ), jest to preferowane. W przeciwnym razie należy go nazwać podobnie do tego, co rozszerza. Na przykład metoda foo_1_1 w @1.1::IFoo może zastąpić funkcjonalność metody foo w @1.0::IFoo , jeśli nie ma lepszej alternatywnej nazwy.

Wersjonowanie na poziomie pakietu

Wersjonowanie HIDL odbywa się na poziomie pakietu; po opublikowaniu pakietu jest on niezmienny (nie można zmieniać jego zestawu interfejsów i UDT). Pakiety mogą być ze sobą powiązane na kilka sposobów, z których każdy można wyrazić poprzez kombinację dziedziczenia na poziomie interfejsu i budowania UDT poprzez kompozycję.

Jednakże jeden typ relacji jest ściśle zdefiniowany i musi być egzekwowany: dziedziczenie wstecznie kompatybilne na poziomie pakietu . W tym scenariuszu pakiet nadrzędny jest pakietem, z którego jest dziedziczony, a pakiet podrzędny jest pakietem rozszerzającym pakiet nadrzędny. Reguły dziedziczenia kompatybilne wstecz na poziomie pakietu są następujące:

  1. Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone z interfejsów pakietu podrzędnego.
  2. Do nowego pakietu można także dodać nowe interfejsy (brak ograniczeń dotyczących powiązań z innymi interfejsami w innych pakietach).
  3. Można także dodać nowe typy danych do wykorzystania w nowych metodach udoskonalonych istniejących interfejsów lub w nowych interfejsach.

Reguły te można zaimplementować przy użyciu dziedziczenia na poziomie interfejsu HIDL i kompozycji UDT, ale wymagają wiedzy na poziomie meta, aby wiedzieć, że te relacje stanowią rozszerzenie pakietu kompatybilne wstecz. Wiedzę tę wnioskuje się w następujący sposób:

Jeśli pakiet spełnia ten wymóg, hidl-gen wymusza zasady zgodności wstecznej.