Android 11 introduce la possibilità di utilizzare AIDL per le HAL in Android, consentendo di implementare parti di Android senza HIDL. Esegui la transizione delle HAL in modo che utilizzino AIDL esclusivamente, ove possibile (quando le HAL upstream utilizzano HIDL, è necessario utilizzare HIDL).
Le HAL che utilizzano AIDL per comunicare tra i componenti del framework, ad esempio quelli in system.img, e i componenti hardware, ad esempio quelli in vendor.img, devono utilizzare AIDL stabile. Tuttavia, per comunicare all'interno di una partizione, ad esempio da una HAL all'altra, non esiste alcuna restrizione sul meccanismo IPC da utilizzare.
Motivazione
AIDL esiste da più tempo di HIDL e viene utilizzato in molti altri contesti, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL supporta la stabilità, è possibile implementare un intero stack con un singolo runtime IPC. AIDL ha anche un sistema di controllo delle versioni migliore rispetto a HIDL. Ecco alcuni vantaggi di AIDL:
- L'utilizzo di un singolo linguaggio IPC significa avere una sola cosa da imparare, di cui eseguire il debug, da ottimizzare e da proteggere.
- AIDL supporta il controllo delle versioni in loco per i proprietari di un'interfaccia:
- I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile controllare le versioni del codice nel corso degli anni e che il costo annuale è inferiore (i tipi possono essere modificati in loco e non sono necessarie librerie aggiuntive per ogni versione dell'interfaccia).
- Le interfacce di estensione possono essere collegate in fase di runtime anziché nel sistema di tipi, quindi non è necessario eseguire il rebase delle estensioni downstream sulle versioni più recenti delle interfacce.
- Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il proprietario sceglie di stabilizzarla. In precedenza, era necessario creare una copia completa dell'interfaccia in HIDL.
Creare una build per il runtime AIDL
AIDL ha tre backend diversi: Java, NDK e CPP. Per utilizzare AIDL stabile,
utilizza sempre la copia di sistema di libbinder in system/lib*/libbinder.so e
comunica su /dev/binder. Per il codice nell'immagine vendor, ciò significa che non è possibile utilizzare libbinder (dal VNDK): questa libreria ha un'API C++ instabile e interni instabili. Il codice fornitore nativo deve invece utilizzare il backend NDK di AIDL, eseguire il collegamento a libbinder_ndk (supportato da libbinder.so di sistema) e alle librerie NDK create dalle voci aidl_interface. Per i nomi esatti dei moduli, consulta Regole di denominazione
dei moduli.
Scrivere un'interfaccia HAL AIDL
Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, sono necessarie due modifiche:
- Ogni definizione di tipo deve essere annotata con
@VintfStability. - La dichiarazione
aidl_interfacedeve includerestability: "vintf",.
Solo il proprietario di un'interfaccia può apportare queste modifiche.
Quando apporti queste modifiche, l'interfaccia deve essere presente nel
manifest VINTF per funzionare. Testa questo requisito (e quelli correlati, ad esempio la verifica che le interfacce rilasciate siano bloccate) utilizzando il
test VTS (Vendor Test Suite) vts_treble_vintf_vendor_test. Puoi utilizzare un'interfaccia @VintfStability senza questi requisiti chiamando AIBinder_forceDowngradeToLocalStability nel backend NDK, android::Stability::forceDowngradeToLocalStability nel backend C++ o android.os.Binder#forceDowngradeToSystemStability nel backend Java su un oggetto binder prima che venga inviato a un altro processo.
Inoltre, per la massima portabilità del codice ed evitare potenziali problemi come librerie aggiuntive non necessarie, disattiva il backend CPP.
Il codice mostra come disattivare il backend CPP:
aidl_interface: {
...
backend: {
cpp: {
enabled: false,
},
},
}
Trovare le interfacce HAL AIDL
Le interfacce AIDL stabili AOSP per le HAL si trovano nelle cartelle aidl nelle stesse directory di base delle interfacce HIDL:
hardware/interfacesè per le interfacce in genere fornite dall'hardware.frameworks/hardware/interfacesè per le interfacce di alto livello fornite all'hardware.system/hardware/interfacesè per le interfacce di basso livello fornite all'hardware.
Inserisci le interfacce di estensione in altre hardware/interfaces
sottodirectory in vendor o hardware.
Interfacce di estensione
A ogni release, Android ha un insieme di interfacce AOSP ufficiali. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò rende il runtime Android incompatibile con il runtime Android AOSP. Evita di modificare queste interfacce in modo che l'immagine GSI possa continuare a funzionare.
Le estensioni possono essere registrate in due modi diversi:
- In fase di runtime; vedi Interfacce di estensione collegate
- Come estensione autonoma, registrata a livello globale e in VINTF
Indipendentemente dalla modalità di registrazione di un'estensione, quando i componenti specifici del fornitore (ovvero non fanno parte di AOSP upstream) utilizzano l'interfaccia, non sono possibili conflitti di unione. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e sono consigliate le seguenti strategie:
- Esegui l'upstream delle aggiunte dell'interfaccia ad AOSP nella prossima release.
- Esegui l'upstream delle aggiunte dell'interfaccia che consentono una maggiore flessibilità (senza conflitti di unione) nella prossima release.
Parcelable di estensione: ParcelableHolder
ParcelableHolder è un'istanza dell'interfaccia Parcelable che può
contenere un'altra istanza di Parcelable.
Il caso d'uso principale di ParcelableHolder è rendere Parcelable estendibile.
Ad esempio, supponiamo che gli implementatori di dispositivi si aspettino di poter estendere un
definito da AOSP Parcelable, AospDefinedParcelable, per includere le loro funzionalità a valore aggiunto.
Utilizza l'interfaccia ParcelableHolder per estendere Parcelable con le tue funzionalità a valore aggiunto. L'interfaccia ParcelableHolder contiene un'istanza di
Parcelable. Se provi ad aggiungere campi direttamente a Parcelable, si verifica un errore:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Come si vede nel codice precedente, questa pratica è interrotta perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando Parcelable viene rivisto nelle prossime release di Android.
Utilizzando ParcelableHolder, il proprietario di un parcelable può definire un punto di estensione in un'istanza di Parcelable:
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Gli implementatori del dispositivo possono quindi definire la propria istanza Parcelable per l'estensione:
parcelable OemDefinedParcelable {
String x;
int[] y;
}
La nuova istanza Parcelable può essere collegata all'originale
Parcelable con il campo 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>();
Nomi delle istanze del server HAL AIDL
Per convenzione, i servizi HAL AIDL hanno un nome di istanza nel formato $package.$type/$instance. Ad esempio, un'istanza della HAL del vibratore viene registrata come android.hardware.vibrator.IVibrator/default.
Scrivere un server HAL AIDL
I server AIDL @VintfStability devono essere dichiarati nel manifest VINTF, ad esempio:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
In caso contrario, devono registrare un servizio AIDL normalmente. Quando esegui i test VTS, è previsto che tutte le HAL AIDL dichiarate siano disponibili.
Scrivere un client AIDL
I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Convertire una HAL esistente da HIDL ad AIDL
Utilizza lo strumento hidl2aidl per convertire un'interfaccia HIDL in AIDL.
Funzionalità di hidl2aidl:
- Crea file AIDL (
.aidl) in base ai file HAL (.hal) per il pacchetto specificato. - Crea regole di build per il pacchetto AIDL appena creato con tutti i backend abilitati.
- Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL.
- Crea regole di build per le librerie di traduzione con le dipendenze richieste.
- Crea asserzioni statiche per garantire che gli enumeratori HIDL e AIDL abbiano gli stessi valori nei backend CPP e NDK.
Per convertire un pacchetto di file HAL in file AIDL:
Crea lo strumento che si trova in
system/tools/hidl/hidl2aidl.La creazione di questo strumento dall'ultima origine offre l'esperienza più completa. Puoi utilizzare l'ultima versione per convertire le interfacce su rami precedenti delle release precedenti:
m hidl2aidlEsegui lo strumento con una directory di output seguita dal pacchetto da convertire.
Facoltativamente, utilizza l'argomento
-lper aggiungere i contenuti di un nuovo file di licenza all'inizio di tutti i file generati. Assicurati di utilizzare la licenza e la data corrette:hidl2aidl -o <output directory> -l <file with license> <package>Ad esempio:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2Leggi i file generati e correggi eventuali problemi di conversione:
conversion.logcontiene eventuali problemi non gestiti da risolvere per primi.- I file AIDL generati potrebbero contenere avvisi e suggerimenti che richiedono un'azione. Questi commenti iniziano con
//. - Libera spazio e apporta miglioramenti al pacchetto.
- Controlla l'
@JavaDeriveannotazione per le funzionalità che potrebbero essere necessarie, ad esempiotoStringoequals.
Crea solo i target di cui hai bisogno:
- Disattiva i backend che non verranno utilizzati. Preferisci il backend NDK al backend CPP ; vedi Creare una build per il runtime AIDL.
- Rimuovi le librerie di traduzione o qualsiasi codice generato che non verrà utilizzato.
Vedi Differenze principali tra AIDL e HIDL:
- L'utilizzo di
Statused eccezioni integrati di AIDL in genere migliora l'interfaccia ed elimina la necessità di un altro tipo di stato specifico dell'interfaccia. - Gli argomenti dell'interfaccia AIDL nei metodi non sono
@nullableper impostazione predefinita come in HIDL.
- L'utilizzo di
SEPolicy per le HAL AIDL
Un tipo di servizio AIDL visibile al codice del fornitore deve avere l'attributo hal_service_type. In caso contrario, la configurazione sepolicy è la stessa di qualsiasi altro servizio AIDL (anche se esistono attributi speciali per le HAL). Ecco un esempio di definizione di un contesto di servizio HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Per la maggior parte dei servizi definiti dalla piattaforma, viene già aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default è già contrassegnato come hal_foo_service). Tuttavia, se un client del framework supporta più nomi di istanza, è necessario aggiungere nomi di istanza aggiuntivi nei file service_contexts specifici del dispositivo:
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Quando crei un nuovo tipo di HAL, devi aggiungere gli attributi HAL. Un attributo HAL specifico potrebbe essere associato a più tipi di servizi (ognuno dei quali può avere più istanze, come appena descritto). Per una HAL, foo, esiste
hal_attribute(foo). Questa macro definisce gli attributi hal_foo_client e hal_foo_server. Per un determinato dominio, le macro hal_client_domain e hal_server_domain associano un dominio a un determinato attributo HAL. Ad esempio, il server di sistema che è un client di questa HAL corrisponde alla policy hal_client_domain(system_server, hal_foo). Un server HAL include in modo simile hal_server_domain(my_hal_domain, hal_foo).
In genere, per un determinato attributo HAL, crea anche un dominio come hal_foo_default per le HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. La distinzione tra i domini per più server è importante solo se esistono più server che forniscono la stessa interfaccia e richiedono un insieme di autorizzazioni diverso nelle loro implementazioni.
In tutte queste macro, hal_foo non è un oggetto sepolicy. Questo token viene invece utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia client-server.
Tuttavia, finora, hal_foo_service e hal_foo (la coppia di attributi di
hal_attribute(foo)) non sono associati. Un attributo HAL è associato
ai servizi HAL AIDL utilizzando la macro hal_attribute_service (le HAL HIDL utilizzano
la macro hal_attribute_hwservice), ad esempio,
hal_attribute_service(hal_foo, hal_foo_service). Ciò significa che i processi hal_foo_client possono ottenere la HAL e i processi hal_foo_server possono registrare la HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore del contesto (servicemanager).
I nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL, ad esempio hal_attribute_service(hal_foo, hal_foo2_service). In generale, poiché ciò implica che i servizi vengono sempre utilizzati insieme, puoi rimuovere hal_foo2_service e utilizzare hal_foo_service per tutti i contesti di servizio. Quando le HAL impostano più istanze hal_attribute_service, è perché il nome dell'attributo HAL originale non è abbastanza generale e non può essere modificato.
Mettendo insieme tutto questo, una HAL di esempio ha il seguente aspetto:
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)
Interfacce di estensione collegate
Un'estensione può essere collegata a qualsiasi interfaccia binder, sia che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore dei servizi sia che si tratti di una sottointerfaccia. Quando ottieni un'estensione, devi verificare che il tipo di estensione sia quello previsto. Puoi impostare le estensioni solo dal processo che gestisce un binder.
Utilizza le estensioni collegate ogni volta che un'estensione modifica la funzionalità di una HAL esistente. Quando è necessaria una funzionalità completamente nuova, questo meccanismo non è necessario e puoi registrare un'interfaccia di estensione direttamente con il gestore dei servizi. Le interfacce di estensione collegate sono più utili quando sono collegate a sottointerfacce, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia binder di un altro servizio richiede una contabilità estesa per fornire funzionalità equivalenti alle estensioni collegate direttamente.
Per impostare un'estensione su un binder, utilizza le seguenti API:
- Backend NDK:
AIBinder_setExtension - Backend Java:
android.os.Binder.setExtension - Backend CPP:
android::Binder::setExtension - Backend Rust:
binder::Binder::set_extension
Per ottenere un'estensione su un binder, utilizza le seguenti API:
- Backend NDK:
AIBinder_getExtension - Backend Java:
android.os.IBinder.getExtension - Backend CPP:
android::IBinder::getExtension - Backend Rust:
binder::Binder::get_extension
Puoi trovare ulteriori informazioni su queste API nella documentazione della funzione getExtension nel backend corrispondente. Un esempio di come utilizzare
le estensioni è disponibile in
hardware/interfaces/tests/extension/vibrator.
Differenze principali tra AIDL e HIDL
Quando utilizzi le HAL AIDL o le interfacce HAL AIDL, tieni presente le differenze rispetto alla scrittura delle HAL HIDL.
- La sintassi del linguaggio AIDL è più simile a Java. La sintassi HIDL è simile a C++.
- Tutte le interfacce AIDL hanno stati di errore integrati. Anziché creare tipi di stato personalizzati, crea numeri interi di stato costanti nei file di interfaccia e utilizza
EX_SERVICE_SPECIFICnei backend CPP e NDK eServiceSpecificExceptionnel backend Java. Vedi Gestione degli errori. - AIDL non avvia automaticamente i pool di thread quando vengono inviati gli oggetti binder. Devi avviarli manualmente (vedi Gestione dei thread).
- AIDL non interrompe l'esecuzione in caso di errori di trasporto non controllati (HIDL
Returninterrompe l'esecuzione in caso di errori non controllati). - AIDL può dichiarare un solo tipo per file.
- Gli argomenti AIDL possono essere specificati come
in,outoinoutoltre a l parametro di output (non esistono callback sincroni). - AIDL utilizza
fdcome tipo primitivo anzichéhandle. - HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per le modifiche compatibili. In AIDL, le modifiche compatibili con le versioni precedenti vengono apportate in loco.
AIDL non ha un concetto esplicito di versioni principali; al contrario, questo è incorporato nei nomi dei pacchetti. Ad esempio, AIDL potrebbe utilizzare il nome del pacchetto
bluetooth2. - AIDL non eredita la priorità in tempo reale per impostazione predefinita. La funzione
setInheritRtdeve essere utilizzata per ogni binder per abilitare l'ereditarietà della priorità in tempo reale.
Test per le HAL
Questa sezione descrive le best practice per testare le HAL. Queste pratiche sono valide anche se il test di integrazione per la tua HAL non è in VTS.
Android si basa su VTS per verificare le implementazioni HAL previste. VTS aiuta a garantire che Android possa essere compatibile con le versioni precedenti delle implementazioni dei fornitori. Le implementazioni che non superano VTS presentano problemi di compatibilità noti che potrebbero impedirne il funzionamento con le versioni future del sistema operativo.
Esistono due parti principali di VTS per le HAL.
1. Verificare che le HAL sul dispositivo siano note e previste da Android
Android si basa su un elenco statico e accurato di tutte le HAL installate. Questo elenco è espresso nel manifest VINTF. Test speciali a livello di piattaforma verificano l'integrità dei livelli HAL nell'intero sistema. Prima di scrivere test specifici per la HAL, devi anche eseguire questi test, in quanto possono indicare se una HAL ha configurazioni VINTF incoerenti.
Questo insieme di test è disponibile in
test/vts-testcase/hal/treble/vintf. Se stai lavorando a un'implementazione HAL del fornitore, utilizza vts_treble_vintf_vendor_test per verificarla. Puoi eseguire questo test con il comando atest vts_treble_vintf_vendor_test.
Questi test sono responsabili della verifica di quanto segue:
- Ogni interfaccia
@VintfStabilitydichiarata in un manifest VINTF è bloccata in una versione rilasciata nota. In questo modo si verifica che entrambe le parti dell'interfaccia siano d'accordo sulla definizione esatta di quella versione dell'interfaccia. Questo è necessario per il funzionamento di base. - Tutte le HAL dichiarate in un manifest VINTF sono disponibili sul dispositivo. Qualsiasi client con autorizzazioni sufficienti per utilizzare un servizio HAL dichiarato deve essere in grado di ottenere e utilizzare questi servizi in qualsiasi momento.
- Tutte le HAL dichiarate in un manifest VINTF forniscono la versione dell'interfaccia dichiarata nel manifest.
- Sul dispositivo non vengono forniti HAL ritirati. Android interrompe il supporto per le versioni precedenti delle interfacce HAL come descritto in Ciclo di vita FCM.
- Le HAL richieste sono presenti sul dispositivo. Alcune HAL sono necessarie per il corretto funzionamento di Android.
2. Verificare il comportamento previsto di ogni HAL
Ogni interfaccia HAL ha i propri test VTS per verificare il comportamento previsto dei client. I casi di test vengono eseguiti su ogni istanza di un'interfaccia HAL dichiarata e applicano un comportamento specifico in base alla versione dell'interfaccia implementata.
In C++, puoi ottenere un elenco di tutte le HAL installate nel sistema con la funzione android::getAidlHalInstanceNames in libaidlvintf_gtest_helper. In Rust, utilizza binder::get_declared_instances.
Questi test tentano di coprire ogni aspetto dell'implementazione HAL su cui si basa il framework Android o su cui potrebbe basarsi in futuro.
Questi test includono la verifica del supporto delle funzionalità, della gestione degli errori e di qualsiasi altro comportamento che un client potrebbe aspettarsi dal servizio.
Traguardi VTS per lo sviluppo HAL
È previsto che i test VTS (o qualsiasi test) vengano aggiornati quando crei o modifichi le interfacce HAL di Android.
I test VTS devono essere completati e pronti per verificare le implementazioni dei fornitori prima di essere bloccati per le release dell'API fornitore Android. Devono essere pronti prima che le interfacce vengano bloccate in modo che gli sviluppatori possano creare le loro implementazioni, verificarle e fornire feedback agli sviluppatori dell'interfaccia HAL.
Test su Seppia
Quando l'hardware non è disponibile, Android utilizza Seppia come veicolo di sviluppo per le interfacce HAL. Ciò consente di eseguire test di integrazione scalabili di Android.
I test hal_implementation_test verificano che Seppia abbia implementazioni delle versioni più recenti dell'interfaccia HAL per assicurarsi che Android sia pronto a gestire le nuove interfacce e che i test VTS siano pronti a testare le nuove implementazioni dei fornitori non appena saranno disponibili nuovi hardware e dispositivi.