AIDL dla licencji HAL

Android 11 umożliwia korzystanie z interfejsu AIDL do obsługi interfejsów HAL w Androidzie. Dzięki temu można wdrażać części Androida bez HIDL. Przejście na używanie wyłącznie interfejsów HAL z AIDL (gdy interfejsy HAL na wyższych poziomach używają HIDL, należy używać HIDL).

Interfejsy HAL korzystające z AIDL do komunikacji między komponentami platformy, takimi jak te w system.img, i komponentami sprzętowymi, takimi jak te w vendor.img, muszą używać stabilnej wersji AIDL. Jednak w ramach partycji, na przykład z jednego HAL do drugiego, nie ma żadnych ograniczeń dotyczących używanego mechanizmu IPC.

Motywacja

Interfejs AIDL istnieje dłużej niż HIDL i jest używany w wielu innych miejscach, np. między komponentami platformy Androida lub w aplikacjach. Teraz, gdy AIDL zapewnia stabilność, można wdrożyć cały pakiet za pomocą jednej obsługi interfejsu IPC. AIDL ma też lepszy system wersji niż HIDL.

  • Korzystanie z jednego języka IPC oznacza, że trzeba się uczyć, debugować, optymalizować i chronić tylko jedną rzecz.
  • AIDL obsługuje wersjonowanie na miejscu dla właścicieli interfejsu:
    • Właściciele mogą dodawać metody na końcu interfejsów lub pola do obiektów Parcelable. Oznacza to, że łatwiej jest wersjonować kod na przestrzeni lat, a koszt na przestrzeni lat jest mniejszy (typy można zmieniać na miejscu i nie trzeba tworzyć dodatkowych bibliotek dla każdej wersji interfejsu).
    • Interfejsy rozszerzeń można dołączać w czasie działania, a nie w systemie typów, więc nie trzeba ponownie bazować rozszerzeń na nowszych wersjach interfejsów.
  • Dotychczasowy interfejs AIDL może być używany bezpośrednio, gdy jego właściciel zdecyduje się na jego stabilizację. Wcześniej trzeba było utworzyć całą kopię interfejsu w HIDL.

Kompilowanie kodu pod środowisko wykonawcze AIDL

AIDL ma 3 różne backendy: Java, NDK i CPP. Aby korzystać z Stabilnego AIDL, musisz zawsze używać kopii systemu libbinder na system/lib*/libbinder.so i talk na /dev/binder. W przypadku kodu w obrazie dostawcy oznacza to, że nie można używać biblioteki libbinder (z VNDK), ponieważ ma ona niestabilny interfejs C++ i niestabilne elementy wewnętrzne. Zamiast tego kod natywny dostawcy musi używać backendu NDK w AIDL, linkować do libbinder_ndk (który jest obsługiwany przez system libbinder.so) oraz linkować do bibliotek NDK utworzonych przez wpisy aidl_interface. Dokładne nazwy modułów znajdziesz w zasadach dotyczących nazewnictwa modułów.

Tworzenie interfejsu AIDL HAL

Aby interfejs AIDL mógł być używany przez system i dostawcę, należy wprowadzić w nim 2 zmiany:

  • Każda definicja typu musi być opatrzona adnotacją @VintfStability.
  • aidl_interface deklaracja musi zawierać stability: "vintf",.

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

Aby te zmiany zadziałały, interfejs musi być w pliku manifestu VINTF. Testuj to (i powiązane wymagania, takie jak weryfikacja, czy opublikowane interfejsy są zamrożone) za pomocą testu VTS vts_treble_vintf_vendor_test. Możesz używać interfejsu @VintfStability bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability w backendzie NDK, android::Stability::forceDowngradeToLocalStability w backendzie C++, android.os.Binder#forceDowngradeToSystemStability w backendzie Java na obiekcie bindera, zanim zostanie on wysłany do innego procesu. W języku Java nie można obniżyć stabilności usługi do poziomu dostawcy, ponieważ wszystkie aplikacje działają w kontekście systemu.

Aby zapewnić maksymalną przenośność kodu i uniknąć potencjalnych problemów, takich jak niepotrzebne dodatkowe biblioteki, wyłącz backend CPP.

Użycie funkcji backends w przykładzie kodu poniżej jest prawidłowe, ponieważ istnieją 3 backendy (Java, NDK i CPP). Poniżej znajdziesz kod, który pokazuje, jak wybrać backend CPP, aby go wyłączyć.

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

Znajdowanie interfejsów AIDL HAL

Stabilne interfejsy AIDL AOSP dla HAL znajdują się w tych samych katalogach podstawowych co interfejsy HIDL, w folderach aidl.

  • sprzęt/interfejsy: interfejsy zwykle udostępniane przez sprzęt;
  • frameworki/sprzęt/interfejsy: interfejsy wysokiego poziomu udostępniane sprzętowi;
  • system/hardware/interfaces: dla interfejsów niskiego poziomu udostępnianych sprzętowi;

Interfejsy rozszerzeń należy umieszczać w innych podkatalogach hardware/interfaces w katalogu vendor lub hardware.

Interfejsy rozszerzeń

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

Rozszerzenia mogą się rejestrować na 2 sposoby:

Jednak gdy rozszerzenie jest zarejestrowane, a interfejs jest używany przez komponenty specyficzne dla dostawcy (czyli niebędące częścią wstecznego AOSP), nie ma możliwości wystąpienia konfliktu podczas scalania. Jednak w przypadku wprowadzania zmian w komponentach AOSP na niższym poziomie mogą wystąpić konflikty podczas scalania. W takich przypadkach zalecamy stosowanie tych strategii:

  • dodatki do interfejsu mogą zostać przesłane do AOSP w kolejnych wersjach
  • dodatki do interfejsu, które zapewniają większą elastyczność bez konfliktów podczas scalania, mogą zostać przesłane w kolei do wersji źródłowej w następnej wersji.

Obiekty parcelable rozszerzenia: ParcelableHolder

ParcelableHolder to Parcelable, który może zawierać inny Parcelable. Głównym zastosowaniem ParcelableHolder jest rozszerzenie możliwości Parcelable. Na przykład implementatorzy urządzeń oczekują, że będą mogli rozszerzyć zdefiniowane przez AOSP interfejsy ParcelableAospDefinedParcelable, aby uwzględnić w nich funkcje zwiększające wartość.

Wcześniej implementatorzy urządzeń nie mogli modyfikować stabilnego interfejsu AIDL zdefiniowanego przez AOSP, ponieważ dodanie kolejnych pól wiązałoby się z błędem:ParcelableHolder

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

Jak widać w poprzednim kodzie, ta metoda nie działa, ponieważ pola dodane przez implementatora urządzenia mogą powodować konflikt, gdy klasa Parcelable zostanie zmieniona w kolejnych wersjach Androida.

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

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

Następnie implementatorzy urządzeń mogą zdefiniować własne Parcelable dla swoich rozszerzeń.

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

Na koniec nowy Parcelable można dołączyć do oryginalnego Parcelable za pomocą pola ParcelableHolder.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Nazwa instancji serwera AIDL HAL

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

Tworzenie serwera HAL AIDL

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

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

W przeciwnym razie należy zarejestrować usługę AIDL w zwykły sposób. Podczas uruchamiania testów VTS należy mieć dostęp do wszystkich zadeklarowanych interfejsów API AIDL HAL.

Tworzenie klienta AIDL

Klienci AIDL muszą zadeklarować się w macierz kompatybilności, na przykład w ten sposób:

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

Konwertowanie istniejącego HAL z HIDL na AIDL

Aby przekonwertować interfejs HIDL na AIDL, użyj narzędzia hidl2aidl.

hidl2aidl funkcji:

  • Tworzenie plików .aidl na podstawie plików .hal w danym pakiecie
  • Utwórz reguły kompilacji dla nowo utworzonego pakietu AIDL ze wszystkimi włączonymi backendami.
  • Utwórz metody tłumaczenia w backendach Java, CPP i NDK, aby przekształcać typy HIDL w typy AIDL.
  • Tworzenie reguł kompilacji dla bibliotek tłumaczeń z wymaganymi zależnościami
  • Utwórz statyczne stwierdzenia, aby mieć pewność, że enumeratory HIDL i AIDL mają te same wartości w backendach CPP i NDK.

Aby przekonwertować pakiet plików HAL na pliki .aidl:

  1. Utwórz narzędzie dostępne w system/tools/hidl/hidl2aidl.

    Utworzenie tego narzędzia na podstawie najnowszego źródła zapewnia najbardziej kompletne wrażenia. Najnowszej wersji możesz używać do konwertowania interfejsów na starszych gałęziach z poprzednich wersji.

    m hidl2aidl
  2. Uruchom narzędzie, podając katalog wyjściowy, a następnie pakiet, który ma zostać przekonwertowany.

    Opcjonalnie możesz użyć argumentu -l, aby dodać zawartość nowego pliku licencji do górnej części wszystkich wygenerowanych plików. Upewnij się, że używasz prawidłowej licencji i daty.

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

    Na przykład:

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

    • conversion.log zawiera wszystkie problemy, które należy rozwiązać jako pierwsze.
    • Wygenerowane pliki .aidl mogą zawierać ostrzeżenia i sugestie, które wymagają podjęcia działań. Te komentarze zaczynają się od //.
    • Wykorzystaj tę okazję, aby uporządkować i ulepszyć pakiet.
    • Sprawdź adnotację @JavaDerive, aby dowiedzieć się, jakie funkcje mogą być potrzebne, np. toString lub equals.
  4. Utwórz tylko te cele, których potrzebujesz.

    • Wyłącz backendy, których nie będziesz używać. Zamiast backendu CPP preferuj backend NDK (patrz wybieranie środowiska uruchomieniowego).
    • Usuń biblioteki tłumaczeń lub wygenerowany kod, który nie będzie używany.
  5. Zapoznaj się z głównymi różnicami między AIDL a HIDL.

    • Korzystanie z wbudowanych Status i wyjątków AIDL zwykle poprawia interfejs i eliminuje potrzebę korzystania z innego typu stanu związanego z interfejsem.
    • Argumenty interfejsu AIDL w metodach nie są domyślnie @nullable, tak jak w HIDL.

SEPolicy dla interfejsów HAL AIDL

Typ usługi AIDL, który jest widoczny dla kodu dostawcy, musi mieć atrybut hal_service_type. W pozostałych przypadkach konfiguracja sepolicy jest taka sama jak w przypadku innych usług AIDL (chociaż w przypadku interfejsów HAL są specjalne atrybuty). Oto przykładowa definicja kontekstu usługi HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

W przypadku większości usług zdefiniowanych przez platformę kontekst usługi z odpowiednim typem jest już dodany (np. android.hardware.foo.IFoo/default jest już oznaczony jako hal_foo_service). Jeśli jednak klient frameworku obsługuje wiele nazw instancji, dodatkowe nazwy instancji należy dodać w plikach service_contexts dotyczących konkretnego urządzenia.

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

Atrybuty HAL należy dodać podczas tworzenia nowego typu HAL. Określony atrybut HAL może być powiązany z wieloma typami usług (każdy z nich może mieć wiele wystąpień). W przypadku HAL foo mamy hal_attribute(foo). To makro definiuje atrybuty hal_foo_clienthal_foo_server. W przypadku danej domeny makro hal_client_domainhal_server_domain kojarzą domenę z danym atrybutem HAL. Na przykład serwer systemowy jest klientem tego interfejsu HAL zgodnie z zasadami hal_client_domain(system_server, hal_foo). Serwer HAL zawiera również hal_server_domain(my_hal_domain, hal_foo). Zwykle dla danego atrybutu HAL tworzymy też domenę, taką jak hal_foo_default, na potrzeby referencji lub przykładowych HAL. Niektóre urządzenia używają jednak tych domen na potrzeby własnych serwerów. Rozróżnianie domen na wielu serwerach ma znaczenie tylko wtedy, gdy mamy wiele serwerów, które obsługują ten sam interfejs i potrzebują różnych zestawów uprawnień w swoich implementacjach. W żadnym z tych makr hal_foo nie jest obiektem sepolicy. Zamiast tego token jest używany przez te makro, aby odwoływać się do grupy atrybutów powiązanych z parą klient-serwer.

Do tej pory nie powiązaliśmy jednak atrybutów hal_foo_servicehal_foo (para atrybutów z hal_attribute(foo)). Atrybut HAL jest powiązany z usługami HAL AIDL za pomocą makra hal_attribute_service (HAL HIDL używa makra hal_attribute_hwservice). Na przykład: hal_attribute_service(hal_foo, hal_foo_service). Oznacza to, że procesy hal_foo_client mogą uzyskać dostęp do HAL, a procesy hal_foo_server mogą zarejestrować HAL. Za stosowanie tych reguł rejestracji odpowiada menedżer kontekstu (servicemanager). Pamiętaj, że nazwy usług nie zawsze odpowiadają atrybutom HAL. Możemy na przykład zobaczyć: hal_attribute_service(hal_foo, hal_foo2_service). Jednak ogólnie, ponieważ zakłada to, że usługi są zawsze używane razem, możemy rozważyć usunięcie hal_foo2_service i używanie hal_foo_service we wszystkich kontekstach usług. Większość HAL, które ustawiają wiele wartości hal_attribute_service, jest spowodowana tym, że pierwotna nazwa atrybutu HAL nie jest wystarczająco ogólna i nie można jej zmienić.

Łącząc to wszystko, przykładowy HAL wygląda tak:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Dołączone interfejsy rozszerzeń

Rozszerzenie może być dołączone do dowolnego interfejsu bindera, niezależnie od tego, czy jest to interfejs najwyższego poziomu zarejestrowany bezpośrednio w menedżerze usługi, czy interfejs podrzędny. Podczas uzyskiwania rozszerzenia musisz potwierdzić, że jego typ jest zgodny z oczekiwaniami. Rozszerzenia można ustawiać tylko w procesie obsługijącym binder.

Załączone rozszerzenia należy stosować, gdy rozszerzenie zmienia działanie istniejącego HAL. Jeśli potrzebna jest zupełnie nowa funkcja, nie musisz używać tego mechanizmu. Interfejs rozszerzenia można zarejestrować bezpośrednio w menedżerze usługi. Dołączone interfejsy rozszerzeń mają największy sens, gdy są dołączone do interfejsów podrzędnych, ponieważ te hierarchie mogą być głębokie lub wieloinstancyjne. Użycie globalnego rozszerzenia do odzwierciedlenia hierarchii interfejsu bindera innej usługi wymagałoby skomplikowanej księgowości, aby zapewnić równoważną funkcjonalność w przypadku bezpośrednio dołączonych rozszerzeń.

Aby skonfigurować rozszerzenie w binderze, użyj tych interfejsów API:

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

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

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

Więcej informacji o tych interfejsach API znajdziesz w dokumentacji funkcji getExtension w odpowiednim backendzie. Przykład użycia rozszerzeń znajdziesz w sekcji sprzęt/interfejsy/testy/rozszerzenie/wibrator.

Najważniejsze różnice między AIDL a HIDL

Podczas korzystania z interfejsów HAL AIDL lub interfejsów AIDL HAL należy pamiętać o różnicach w porównaniu z pisaniem interfejsów HAL HIDL.

  • Składnia języka AIDL jest zbliżona do składni języka Java. Składnia HIDL jest podobna do C++.
  • Wszystkie interfejsy AIDL mają wbudowane stany błędów. Zamiast tworzyć niestandardowe typy stanu, utwórz w plikach interfejsu stałe typy int o stanie i użyj ich w backendach CPP/NDK (EX_SERVICE_SPECIFIC) oraz w backendzie Java (ServiceSpecificException). Zobacz Obsługa błędów.
  • AIDL nie uruchamia automatycznie puli wątków podczas wysyłania obiektów bindera. Należy je uruchamiać ręcznie (patrz zarządzanie wątkami).
  • AIDL nie przerywa operacji w przypadku niesprawdzonych błędów transportu (HIDL Return przerywa operacje w przypadku niesprawdzonych błędów).
  • AIDL może zadeklarować tylko jeden typ na plik.
  • Argumenty AIDL można określić jako in/out/inout oprócz parametru wyjściowego (nie ma „wywołań zwrotnych synchronicznych”).
  • AIDL używa fd jako typu prymitywnego zamiast uchwytu.
  • HIDL używa wersji głównych w przypadku zmian niezgodnych i wersji podrzędnych w przypadku zmian zgodnych. W AIDL zmiany zgodne wstecznie są wprowadzane na miejscu. AIDL nie ma wyraźnej koncepcji wersji głównych; zamiast tego są one uwzględniane w nazwach pakietów. Na przykład AIDL może używać nazwy pakietu bluetooth2.
  • Domyślnie AIDL nie dziedziczy priorytetu w czasie rzeczywistym. Aby umożliwić dziedziczenie priorytetów w czasie rzeczywistym, należy użyć funkcji setInheritRt w każdym binderze.

Pakiet testów dostawcy (VTS) dla HAL

Android korzysta z pakietu testów dostawcy (VTS) do weryfikowania implementacji HAL. VTS zapewnia zgodność wsteczną Androida ze starszymi implementacjami dostawców. W przypadku implementacji, które nie przechodzą testu VTS, występują znane problemy ze zgodnością, które mogą uniemożliwić im działanie w przyszłych wersjach systemu operacyjnego.

VTS dla HAL składa się z 2 głównych części.

1. Sprawdź, czy interfejsy HAL na urządzeniu są znane i oczekywane przez Androida.

Ten zestaw testów znajdziesz w test/vts-testcase/hal/treble/vintf. Są odpowiedzialni za weryfikację:

  • Każdy interfejs @VintfStability zadeklarowany w pliku manifestu VINTF jest zamrożony w znanej wersji. Dzięki temu obie strony interfejsu zgadzają się co do dokładnej definicji tej wersji interfejsu. Jest to konieczne do podstawowego działania.
  • Na tym urządzeniu dostępne są wszystkie HAL-e zadeklarowane w manifeście VINTF. Każdy klient, który ma wystarczające uprawnienia do korzystania z deklarowanej usługi HAL, musi mieć możliwość uzyskania dostępu do tych usług i korzystania z nich w dowolnym momencie.
  • Wszystkie interfejsy HAL zadeklarowane w pliku manifestu VINTF obsługują wersję interfejsu zadeklarowaną w pliku manifestu.
  • Na urządzeniu nie są wyświetlane żadne przestarzałe interfejsy HAL. Android przestaje obsługiwać starsze wersje interfejsów HAL zgodnie z opisem w artykule Cykl życia usługi FCM.
  • Wymagane interfejsy HAL są obecne na urządzeniu. Niektóre interfejsy HAL są wymagane do prawidłowego działania Androida.

2. Sprawdź oczekiwane działanie każdego interfejsu HAL.

Każdy interfejs HAL ma własne testy VTS, które weryfikują oczekiwane działanie klientów. Przypadki testowe są uruchamiane w przypadku każdej instancji zadeklarowanego interfejsu HAL i wymuszają określone działanie na podstawie wersji zaimplementowanego interfejsu.

Testy te obejmują wszystkie aspekty implementacji HAL, na których opiera się framework Androida lub które mogą być używane w przyszłości.

Testy te obejmują weryfikację obsługi funkcji, obsługi błędów i innych zachowań, których klient może oczekiwać od usługi.

Etapy VTS w przypadku rozwoju HAL

Testy VTS powinny być aktualizowane podczas tworzenia lub modyfikowania interfejsów HAL systemu Android.

Testy VTS muszą być ukończone i gotowe do weryfikacji implementacji dostawców, zanim zostaną zamrożone w przypadku wersji interfejsu API dostawcy Androida. Muszą być gotowe przed zamrożeniem interfejsów, aby deweloperzy mogli tworzyć swoje implementacje, weryfikować je i przekazywać opinie deweloperom interfejsu HAL.

VTS w Cuttlefish

Gdy sprzęt jest niedostępny, Android używa Cuttlefish jako narzędzia do tworzenia interfejsów HAL. Umożliwia to testowanie VTS i testy integracji Androida na dużą skalę. hal_implementation_test sprawdza, czy Cuttlefish ma implementacje najnowszych wersji interfejsu HAL, aby upewnić się, że Android jest gotowy do obsługi nowych interfejsów, oraz czy testy VTS są gotowe do testowania implementacji nowych dostawców, gdy tylko pojawią się nowe wersje sprzętu i urządzeń.