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 o nazwie
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.
- 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
- 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 jakoandroid.hardware.bar@1.0::S
i znajduje się wbar/1.0/types.hal
(ponieważtypes.hal
jest importowany automatycznie). -
IFooCallback
jest interpolowany jakoandroid.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 wtypes.hal
). Tak więc reguła 3 rozwiązuje go doandroid.hardware.foo@1.0::IFooCallback
, który jest importowany przezimport 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 jegotypes.hal
) -
types.hal
zandroid.hardware.baz@1.0::types
(interfejsy wandroid.hardware.baz@1.0
nie są importowane) -
IQux.hal
itypes.hal
zandroid.hardware.qux@1.0
-
Quuz
zandroid.hardware.quuz@1.0
(zakładając, żeQuuz
jest zdefiniowany wtypes.hal
, cały pliktypes.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. |
---|
Reguła B | Wszystkie poniższe są prawdziwe:
|
---|
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 kataloghardware/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:
- Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone przez interfejsy w pakiecie podrzędnym.
- Nowe interfejsy można również dodać do nowego pakietu (brak ograniczeń dotyczących relacji do innych interfejsów w innych pakietach).
- 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.