Obsługa wersji

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

HIDL wymaga, aby każdy interfejs napisany w HIDL był wersjonowany. Po opublikowaniu interfejsu HAL zostaje on zamrożony i wszelkie dalsze zmiany muszą zostać wprowadzone do nowej wersji tego interfejsu. Chociaż danego opublikowanego interfejsu nie można modyfikować, 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 pierwotnych typów danych, których można używać do tworzenia bardziej złożonych typów za pomocą struktur, związków i wyliczeń . UDT są przekazywane do metod interfejsów i mogą być definiowane na poziomie pakietu (wspólne dla wszystkich interfejsów) lub lokalnie dla interfejsu.
  • Interfejsy . Jako podstawowy blok konstrukcyjny 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:
    • Plik definicji typu danych o nazwie types.hal .
    • Zero lub więcej interfejsów, każdy w swoim własnym 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 (takiej jak 1.0 ), jest niezmienny; nie można go zmienić. Modyfikacje interfejsów w pakiecie lub jakiekolwiek zmiany jego UDT mogą mieć miejsce 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 poprawek i składników metadanych kompilacji. Wewnątrz danego pakietu, podbicie wersji podrzędnej oznacza, że ​​nowa wersja pakietu jest wstecznie kompatybilna ze starym pakietem, a podbicie wersji głównej oznacza, że ​​nowa wersja pakietu nie jest zgodna wstecz ze starym pakietem.

Koncepcyjnie pakiet może odnosić się do innego pakietu na kilka sposobów:

  • Wcale nie .
  • Rozszerzalność zgodna z poprzednimi wersjami na poziomie pakietu . Dzieje się tak w przypadku nowych podrzędnych wersji uprev (następna zwiększona wersja) pakietu; nowy pakiet ma taką samą nazwę i wersję główną jak stary pakiet, ale wyższą wersję pomocniczą. Funkcjonalnie nowy pakiet jest nadzbiorem starego pakietu, co oznacza:
    • Interfejsy najwyższego poziomu pakietu nadrzędnego są obecne w nowym pakiecie, chociaż interfejsy mogą mieć nowe metody, nowe interfejsy lokalne UDT (rozszerzenie poziomu interfejsu opisane poniżej) i nowe UDT w types.hal .
    • Do nowego pakietu można również dodać 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.
    • Nowe typy danych mogą być również dodawane do wykorzystania przez nowe metody zaktualizowanych istniejących interfejsów lub przez nowe interfejsy.
  • Rozszerzalność zgodna z poprzednimi wersjami na poziomie interfejsu . Nowy pakiet może również rozszerzyć oryginalny pakiet, składając się z logicznie oddzielnych interfejsów, które po prostu zapewniają dodatkową funkcjonalność, a nie podstawową. W tym celu mogą być pożądane:
    • Interfejsy w nowym pakiecie wymagają odwołania się do typów danych starego pakietu.
    • Interfejsy w nowym pakiecie mogą rozszerzać interfejsy jednego lub więcej starych pakietów.
  • Rozszerz oryginalną niezgodność wsteczną . Jest to główna wersja pakietu i nie musi istnieć między nimi żadna korelacja. W takim stopniu, w jakim istnieje, może być wyrażona kombinacją typów ze starszej wersji pakietu oraz dziedziczeniem podzbioru interfejsów ze starego pakietu.

Strukturyzacja interfejsów

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

Treble obsługuje oddzielnie kompilowane komponenty dostawcy i systemu, w których vendor.img na urządzeniu i system.img mogą być kompilowane oddzielnie. Wszystkie interakcje mię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 przekazywane przez HIDL muszą być jawnie zdefiniowane. Aby zapewnić implementację i możliwość dalszej współpracy klienta, nawet jeśli zostały skompilowane oddzielnie lub opracowane niezależnie, dane muszą być zgodne z następującymi wymaganiami:

  • Można opisać bezpośrednio w HIDL (za pomocą wyliczeń struktur itp.) z nazwami semantycznymi i znaczeniem.
  • Można opisać za pomocą standardu publicznego, takiego jak ISO/IEC 7816.
  • Można opisać standardem sprzętowym lub fizycznym układem sprzętu.
  • W razie potrzeby mogą to być dane nieprzezroczyste (takie jak klucze publiczne, identyfikatory itp.).

Jeśli używane są dane nieprzezroczyste, muszą być odczytywane tylko z jednej strony interfejsu HIDL. Na przykład, jeśli kod vendor.img daje komponentowi system.img komunikat ciągu lub dane vec<uint8_t> , dane te nie mogą być analizowane przez sam system.img ; można go tylko przekazać 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 warstwy HAL, używając tylko plików .hal (tj. nie powinieneś patrzeć na źródła Androida ani publiczne standardy). Zalecamy określenie dokładnie wymaganego zachowania. Stwierdzenia takie jak „implementacja może zrobić A lub B” zachęcają implementacje do łączenia się z klientami, z którymi są tworzone.

Układ kodu HIDL

HIDL zawiera pakiety podstawowe i dostawców.

Podstawowe interfejsy HIDL to interfejsy określone przez Google. Pakiety, do których należą, zaczynają się od android.hardware. i są nazwane przez podsystem, potencjalnie z zagnieżdżonymi poziomami nazewnictwa. Na przykład pakiet NFC nazywa się android.hardware.nfc , a pakiet aparatu to android.hardware.camera . Zasadniczo pakiet podstawowy ma nazwę android.hardware. [ name1 ].[ name2 ]…. Pakiety HIDL mają oprócz nazwy 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 umieszczenie w drzewie źródłowym.

Wszystkie pakiety podstawowe są umieszczane w systemie kompilacji pod hardware/interfaces/ . Pakiet android.hardware. [ name1 ].[ name2 ]… w wersji $m.$n znajduje się pod hardware/interfaces/name1/name2//$m.$n/ ; Pakiet android.hardware.camera w wersji 3.4 znajduje się w katalogu hardware/interfaces/camera/3.4/. Zakodowane mapowanie istnieje między prefiksem pakietu android.hardware. i ścieżka hardware/interfaces/ .

Pakiety inne niż podstawowe (dostawcy) to te, które są produkowane przez dostawcę SoC lub ODM. Prefiksem pakietów innych niż podstawowe jest vendor.$(VENDOR).hardware. gdzie $(VENDOR) odnosi się do dostawcy SoC lub OEM/ODM. To mapuje na ścieżkę vendor/$(VENDOR)/interfaces w drzewie (to mapowanie jest również zakodowane).

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

W 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 zadeklarowane są wystąpienia typu, a nie tam, gdzie zdefiniowany jest sam typ. Na przykład załóżmy, że pakiet android.hardware.nfc, wersja 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 rozdzielona kropkami nazwa pakietu HIDL (np. android.hardware.nfc ).
  • VERSION to format rozdzielonej kropkami wersji głównej.pobocznej pakietu (np. 1.0 ).
  • UDT to rozdzielona kropkami nazwa HIDL UDT. Ponieważ HIDL obsługuje zagnieżdżone UDT, a interfejsy HIDL mogą zawierać UDT (rodzaj zagnieżdżonej deklaracji), kropki są używane do uzyskania dostępu do nazw.

Na przykład, jeśli następująca zagnieżdżona deklaracja została zdefiniowana w pliku typów wspólnych w pakiecie android.hardware.example wersja 1.0 :

// 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 znajduje 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że być określany jako Bar tylko w zakresie deklaracji Foo . Na poziomie pakietu lub interfejsu musisz odwołać się do Bar via Foo : Foo.Bar , tak jak w powyższej deklaracji metody doSomething . 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 enum, każda wartość typu enum ma w pełni kwalifikowaną nazwę, która zaczyna się od w pełni kwalifikowanej nazwy typu enum, po której następuje dwukropek, a następnie nazwa wartości enum. Na przykład załóżmy, że pakiet android.hardware.nfc, wersja 1.0 definiuje typ wyliczenia 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 jest dokładnie taką samą w pełni kwalifikowaną nazwą dla typu wyliczenia.
  • VALUE to nazwa wartości.

Reguły autownioskowania

W pełni kwalifikowana nazwa UDT nie musi być określana. 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ę przy użyciu reguł autointerferencji (niższy numer reguły oznacza wyższy priorytet).

Zasada nr 1

Jeśli nie podano pakietu i wersji, następuje próba wyszukania nazwy lokalnej. Przykład:

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

NfcErrorMessage jest wyszukiwany lokalnie i znajduje się nad nim typedef . NfcData jest również wyszukiwana lokalnie, ale ponieważ nie jest zdefiniowana lokalnie, używane są reguły 2 i 3. @1.0::NfcStatus udostępnia wersję, więc zasada 1 nie ma zastosowania.

Zasada 2

Jeśli reguła 1 nie powiedzie się i brakuje składnika w pełni kwalifikowanej nazwy (pakietu, wersji lub pakietu i wersji), składnik jest automatycznie wypełniany informacjami z bieżącego pakietu. Kompilator HIDL następnie przeszukuje bieżący plik (i wszystkie importy), aby znaleźć automatycznie wypełnioną w pełni kwalifikowaną nazwę. Korzystając z powyższego przykładu, załóżmy, że deklaracja ExtendedNfcData została wykonana 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 w celu utworzenia w pełni kwalifikowanej nazwy UDT android.hardware.nfc@1.0::NfcData . Ponieważ nazwa istnieje w aktualnym pakiecie (zakładając, że jest importowana poprawnie), jest używana do deklaracji.

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

  • Jest importowany jawnie za pomocą instrukcji import .
  • Jest zdefiniowany w types.hal w aktualnym pakiecie

Ten sam proces ma miejsce, jeśli NfcData została zakwalifikowana tylko przez numer 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 zapewni dopasowania (UDT nie jest zdefiniowany w bieżącym pakiecie), kompilator HIDL skanuje w poszukiwaniu dopasowania we wszystkich importowanych 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 powinien (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 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 uprzywilejowany przed importowanym typem 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 interpolowane 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 (jak w types.hal ). Tak więc reguła 3 rozwiązuje go do android.hardware.foo@1.0::IFooCallback , który jest importowany przez import android.hardware.foo@1.0; ).

typy.hal

Każdy pakiet HIDL zawiera plik types.hal zawierający UDT, które są współużytkowane przez wszystkie interfejsy uczestniczące 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 jest przeznaczony do opisywania publicznego API pakietu, ale raczej do hostowania UDT używanych 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 są z definicji na poziomie 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 (niejawnie)
  • android.hardware.foo@1.0::types (niejawnie)
  • Wszystko w android.hardware.bar@1.0 (w tym wszystkie interfejsy i jego 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 , cały plik types.hal jest analizowany, ale typy inne niż Quuz nie są importowane).

Wersjonowanie na poziomie interfejsu

Każdy interfejs w pakiecie znajduje się w osobnym pliku. Pakiet, do którego należy interfejs, jest zadeklarowany w górnej części interfejsu za pomocą instrukcji package . Po deklaracji pakietu można wymienić zero lub więcej importów na poziomie interfejsu (częściowy lub cały pakiet). Na przykład:

package android.hardware.nfc@1.0;

W HIDL interfejsy mogą dziedziczyć z innych interfejsów za pomocą słowa kluczowego extends . Aby interfejs rozszerzał inny interfejs, musi mieć do niego dostęp za pośrednictwem instrukcji import . Nazwa rozszerzanego interfejsu (interfejs bazowy) jest zgodna z opisanymi powyżej regułami określania nazwy typu. Interfejs może dziedziczyć tylko z jednego interfejsu; HIDL nie obsługuje wielokrotnego dziedziczenia.

Poniższe przykłady wersjonowania uprev używają 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 uprev

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

Reguła A „Jest początkową wersją pomocniczą”: wszystkie poprzednie wersje pomocnicze, package@major.0 , package@major.1 , …, package@major.(minor-1) nie mogą być zdefiniowane.
LUB
Reguła B

Wszystkie poniższe są prawdziwe:

  1. „Poprzednia wersja pomocnicza jest ważna”: package@major.(minor-1) musi być zdefiniowany i przestrzegać tej samej reguły A (żaden z package@major.0 do package@major.(minor-2) nie jest zdefiniowany) ani reguły B (jeśli jest to uprev z @major.(minor-2) );

    ORAZ

  2. "Odziedzicz przynajmniej jeden interfejs o tej samej nazwie": istnieje interfejs package@major.minor::IFoo rozszerzający package@major.(minor-1)::IFoo (jeśli poprzedni pakiet ma interfejs);

    ORAZ

  3. "Brak dziedziczonego 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, że nie istnieje żaden IBar z mniejszą liczbą k.

Z powodu zasady A:

  • Pakiet może zaczynać się od dowolnego drugorzędnego 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 nie powinien nawet istnieć.

Jednak reguła A nie wpływa na pakiet o tej samej nazwie pakietu, ale o innej wersji głównej (na przykład android.hardware.camera.device ma zdefiniowane zarówno @1.0 , jak i @3.2 ; @3.2 nie wymaga interakcji z @1.0 .) Stąd @3.2::IExtFoo może rozszerzać @1.0::IFoo .

Zakładając, że nazwa pakietu jest inna, package@major.minor::IBar może dziedziczyć 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 deklaruje jawnie supertypu za pomocą słowa kluczowego extend , rozszerzy on android.hidl.base@1.0::IBase (z wyjątkiem samego IBase ).

B.2 i B.3 muszą być przestrzegane w tym samym czasie. 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 aktualizacja.

Ulepszanie interfejsów

Aby podnieść wersję android.hardware.example@1.0 (zdefiniowaną 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);
}

Jest to import na poziomie pakietu wersji 1.0 android.hardware.example w types.hal . Chociaż w wersji 1.1 pakietu nie są dodawane żadne nowe UDT, nadal potrzebne są odwołania do UDT w wersji 1.0 , stąd import na poziomie pakietu w types.hal . (Ten sam efekt można było osiągnąć za pomocą importu na poziomie interfejsu w IQuux.hal .)

W extends @1.0::IQuux w deklaracji IQuux określiliśmy dziedziczoną wersję IQuux (ujednoznacznienie jest wymagane, ponieważ IQuux jest używany do deklarowania interfejsu i dziedziczenia po interfejsie). Ponieważ deklaracje są po prostu nazwami, które dziedziczą wszystkie atrybuty pakietu i wersji w miejscu deklaracji, ujednoznacznienie musi znajdować się w nazwie interfejsu podstawowego; moglibyśmy również użyć w pełni kwalifikowanego UDT, ale byłoby to zbędne.

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

Konwencje Uprev

Czasami nazwy interfejsów muszą zmienić nazwę rozszerzającego interfejsu. Zalecamy, aby rozszerzenia wyliczenia, struktury i związki miały taką samą nazwę, jak rozszerzenia, chyba że są wystarczająco różne, aby uzasadnić 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 ), to jest to preferowane. W przeciwnym razie powinien być nazwany 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 niezmienny (nie można zmienić jego zestawu interfejsów i UDT). Pakiety mogą odnosić się do siebie na kilka sposobów, z których wszystkie można wyrazić poprzez kombinację dziedziczenia na poziomie interfejsu i budowania UDT przez kompozycję.

Jednak jeden typ relacji jest ściśle zdefiniowany i musi być wymuszony: Dziedziczenie zgodne z poprzednimi wersjami na poziomie pakietu . W tym scenariuszu pakiet nadrzędny jest pakietem dziedziczonym, a pakiet podrzędny rozszerza pakiet nadrzędny. Reguły dziedziczenia zgodne z poprzednimi wersjami na poziomie pakietu są następujące:

  1. Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone przez interfejsy w pakiecie podrzędnym.
  2. Nowe interfejsy można również dodać do nowego pakietu (brak ograniczeń dotyczących relacji do innych interfejsów w innych pakietach).
  3. Nowe typy danych mogą być również dodawane do wykorzystania przez nowe metody zaktualizowanych istniejących interfejsów lub przez nowe interfejsy.

Reguły te można zaimplementować przy użyciu dziedziczenia na poziomie interfejsu HIDL i kompozycji UDT, ale wymaga wiedzy na poziomie meta, aby wiedzieć, że te relacje stanowią rozszerzenie pakietu zgodne z poprzednimi wersjami. Ta wiedza jest wywnioskowana w następujący sposób:

Jeśli pakiet spełnia to wymaganie, hidl-gen wymusza reguły kompatybilności wstecznej.