Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android. In questo modo, è possibile implementare parti di Android senza HIDL. Passare alle HAL che utilizzano AIDL esclusivamente, se possibile (quando le HAL a monte utilizzano HIDL, deve essere utilizzato HIDL).
Gli HAL che utilizzano AIDL per comunicare tra i componenti del framework, come quelli in system.img
, e i componenti hardware, come quelli in vendor.img
, devono utilizzare AIDL stabile. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non ci sono limitazioni sul meccanismo IPC da utilizzare.
Motivazione
AIDL esiste da più tempo di HIDL e viene utilizzato in molti altri luoghi, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL supporta la stabilità, è possibile implementare un intero stack con un unico runtime IPC. AIDL ha anche un sistema di gestione delle versioni migliore di HIDL.
- L'utilizzo di un unico linguaggio IPC significa avere un solo elemento da imparare, eseguire il debug, ottimizzare e proteggere.
- AIDL supporta il controllo della versione in-place per i proprietari di un'interfaccia:
- I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile gestire le versioni del codice nel corso degli anni e anche il costo annuale è inferiore (i tipi possono essere modificati in situ e non è necessaria alcuna libreria aggiuntiva per ogni versione dell'interfaccia).
- Le interfacce di estensione possono essere collegate in fase di esecuzione anziché nel sistema di tipi, quindi non è necessario eseguire il rebase delle estensioni a valle sulle versioni più recenti delle interfacce.
- Un'interfaccia AIDL esistente può essere utilizzata direttamente se il proprietario sceglie di stabilizzarla. In precedenza, era necessario creare un'intera copia dell'interfaccia in HIDL.
Esegui la compilazione in base al runtime AIDL
AIDL ha tre diversi backend: Java, NDK e CPP. Per utilizzare Stable AIDL, devi sempre utilizzare la copia di sistema di libbinder in system/lib*/libbinder.so
e comunicare su /dev/binder
. Per il codice nell'immagine del fornitore, ciò significa che non è possibile utilizzare libbinder
(del VNDK): questa libreria ha un'API C++ instabile e componenti interni instabili. Il codice del fornitore nativo deve invece utilizzare il backend NDK di
AIDL, eseguire il collegamento a libbinder_ndk
(supportato dal sistema libbinder.so
)
e eseguire il collegamento alle librerie NDK create dalle voci aidl_interface
. Per i nomi esatti dei moduli, consulta le regole di denominazione dei moduli.
Scrivere un'interfaccia HAL AIDL
Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, è necessario apportare due modifiche:
- Ogni definizione di tipo deve essere annotata con
@VintfStability
. - La dichiarazione
aidl_interface
deve includerestability: "vintf",
.
Solo il proprietario di un'interfaccia può apportare queste modifiche.
Quando apporti queste modifiche, l'interfaccia deve essere nel
file manifest VINTF per funzionare. Testa questo requisito (e i relativi requisiti, ad esempio la verifica che le interfacce rilasciate siano bloccate) utilizzando il test VTS 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. Il downgrade di un servizio
alla stabilità del fornitore non è supportato in Java perché tutte le app vengono eseguite in un
contesto di sistema.
Inoltre, per la massima portabilità del codice e per evitare potenziali problemi come librerie aggiuntive non necessarie, disattiva il backend CPP.
Tieni presente che l'utilizzo di backends
nell'esempio di codice riportato di seguito è corretto, in quanto esistono tre backend (Java, NDK e CPP). Il codice riportato di seguito indica come selezionare in modo specifico il backend CPP per disattivarlo.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Trovare le interfacce HAL AIDL
Le interfacce AIDL stabili di AOSP per gli HAL si trovano nelle stesse directory di base delle interfacce HIDL, nelle cartelle aidl
.
- 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 a basso livello fornite all'hardware
Devi inserire le interfacce di estensione in altre sottodirectory di hardware/interfaces
in vendor
o hardware
.
Interfacce di estensione
Android ha un insieme di interfacce AOSP ufficiali con ogni release. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android è incompatibile con il runtime Android AOSP. Per i dispositivi GMS, evitare di modificare queste interfacce è anche ciò che garantisce il funzionamento dell'immagine GSI.
Le estensioni possono registrarsi in due modi diversi:
- in fase di esecuzione, consulta le estensioni collegate.
- autonomi, registrati a livello globale e in VINTF.
Indipendentemente da come viene registrata un'estensione, quando i componenti specifici del fornitore (ovvero non facenti parte dell'AOSP a monte) utilizzano l'interfaccia, non è possibile che si verifichino conflitti di unione. Tuttavia, quando vengono apportate modifiche a valle ai componenti AOSP a monte, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:
- le aggiunte all'interfaccia possono essere trasferite ad AOSP nella prossima release
- le aggiunte all'interfaccia che consentono ulteriore flessibilità, senza conflitti di unione, possono essere trasferite nella release successiva
Parcelable dell'estensione: ParcelableHolder
ParcelableHolder
è un Parcelable
che può contenere un altro Parcelable
.
Il caso d'uso principale di ParcelableHolder
è rendere un Parcelable
estensibile.
Ad esempio, immagina che gli implementatori dei dispositivi si aspettino di poter estendere un Parcelable
, AospDefinedParcelable
definito da AOSP, per includere le loro funzionalità con valore aggiunto.
In precedenza, senza ParcelableHolder
, gli implementatori dei dispositivi non potevano modificare
un'interfaccia AIDL stabile definita da AOSP perché sarebbe stato un errore aggiungere altri
campi:
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 non funziona perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando Parcelable viene rivisto nelle release successive di Android.
Utilizzando ParcelableHolder
, il proprietario di un parcelable può definire un punto di estensione in un Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Gli implementatori dei dispositivi possono quindi definire il proprio Parcelable
per la propria estensione.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Infine, il nuovo Parcelable
può essere collegato all'Parcelable
originale 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 istanza del formato
$package.$type/$instance
. Ad esempio, un'istanza dell'HAL del vibratore è registrata come android.hardware.vibrator.IVibrator/default
.
Scrivere un server HAL AIDL
@VintfStability
I server AIDL devono essere dichiarati nel file manifest VINTF, ad esempio come segue:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
In caso contrario, deve registrare un servizio AIDL normalmente. Quando esegui test VTS, è previsto che tutti gli HAL AIDL dichiarati siano disponibili.
Scrivere un client AIDL
I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio come segue:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Convertire un HAL esistente da HIDL ad AIDL
Utilizza lo strumento hidl2aidl
per convertire un'interfaccia HIDL in AIDL.
Funzionalità di hidl2aidl
:
- Crea file
.aidl
in base ai file.hal
per il pacchetto specificato - Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend attivati
- Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL
- Crea regole di compilazione per le librerie di traduzione con le dipendenze richieste
- Crea assert statici per assicurarti 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 in
system/tools/hidl/hidl2aidl
.La creazione di questo strumento dall'origine più recente offre l'esperienza più completa. Puoi utilizzare la versione più recente per convertire le interfacce dei branch precedenti delle release precedenti.
m hidl2aidl
Esegui lo strumento con una directory di output seguita dal pacchetto da convalidare.
Se vuoi, utilizza l'argomento
-l
per aggiungere i contenuti di un nuovo file di licenza nella parte superiore 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.2
Esamina i file generati e risolvi eventuali problemi di conversione.
conversion.log
contiene eventuali problemi non gestiti da risolvere per primi.- I file
.aidl
generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un'azione. Questi commenti iniziano con//
. - Cogli l'occasione per riorganizzare e migliorare il pacchetto.
- Controlla l'annotazione
@JavaDerive
per le funzionalità che potrebbero essere necessarie, ad esempiotoString
oequals
.
Crea solo i target di cui hai bisogno.
- Disattiva i backend che non verranno utilizzati. Se preferisci il backend NDK rispetto al backend CPP, consulta la sezione Scegliere il runtime.
- Rimuovi le librerie di traduzione o il codice generato che non verrà utilizzato.
Consulta Differenze principali tra AIDL/HIDL.
- L'utilizzo di
Status
e delle eccezioni integrate di AIDL in genere migliora l'interfaccia e elimina la necessità di un altro tipo di stato specifico per l'interfaccia. - Gli argomenti dell'interfaccia AIDL nei metodi non sono
@nullable
per impostazione predefinita come accadeva in HIDL.
- L'utilizzo di
SEPolicy per HAL AIDL
Un tipo di servizio AIDL visibile al codice del fornitore deve avere l'attributohal_service_type
. In caso contrario, la configurazione di sepolicy è la stessa di qualsiasi altro servizio AIDL (anche se esistono attributi speciali per gli 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, è già stato aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default
sarebbe già contrassegnato come hal_foo_service
). Tuttavia, se un client del framework supporta più nomi di istanze, è necessario aggiungere altri nomi di istanze nei file service_contexts
specifici del dispositivo.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Gli attributi HAL devono essere aggiunti quando viene creato un nuovo tipo di HAL. Un attributo HAL specifico può essere associato a più tipi di servizi (ognuno dei quali può avere più istanze, come abbiamo appena discusso). Per un HAL, foo
, abbiamo
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 questo HAL corrisponde al criteriohal_client_domain(system_server, hal_foo)
. Analogamente, un server HAL includehal_server_domain(my_hal_domain, hal_foo)
. In genere, per un determinato attributo HAL, creiamo anche un dominio come hal_foo_default
per riferimento o HAL di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server.
Distinguere i domini per più server è importante solo se abbiamo 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 di server client.
Tuttavia, finora non abbiamo associato hal_foo_service
e hal_foo
(la coppia di attributi di hal_attribute(foo)
). Un attributo HAL è associato
ai servizi HAL AIDL utilizzando la macro hal_attribute_service
(i HAL HIDL utilizzano
la macro hal_attribute_hwservice
). Ad esempio,
hal_attribute_service(hal_foo, hal_foo_service)
. Ciò significa che
hal_foo_client
i processi possono acquisire l'HAL e hal_foo_server
i processi possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore del contesto (servicemanager
). Tieni presente che i nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL. Ad esempio, potremmo vedere
hal_attribute_service(hal_foo, hal_foo2_service)
. In genere, poiché questo implica che i servizi vengono sempre utilizzati insieme, potremmo prendere in considerazione la rimozione di hal_foo2_service
e l'utilizzo di hal_foo_service
per tutti i contesti dei nostri servizi. La maggior parte degli HAL che imposta più hal_attribute_service
è dovuta al fatto che il nome dell'attributo HAL originale non è abbastanza generico e non può essere modificato.
Mettendo tutto insieme, un 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 estensioni collegate
Un'estensione può essere collegata a qualsiasi interfaccia del binder, che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ricevi un'estensione, devi verificare che il tipo di estensione sia come previsto. Le estensioni possono essere impostate solo dal processo che serve un binder.
Le estensioni allegate devono essere utilizzate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, non è necessario utilizzare questo meccanismo e un'interfaccia di estensione può essere registrata direttamente con il gestore del servizio. Le interfacce di estensioni collegate hanno più senso 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 del binder di un altro servizio richiederebbe un'ampia contabilità per fornire funzionalità equivalenti alle estensioni collegate direttamente.
Per impostare un'estensione sul binder, utilizza le seguenti API:
- Nel backend NDK:
AIBinder_setExtension
- Nel backend Java:
android.os.Binder.setExtension
- Nel backend CPP:
android::Binder::setExtension
- Nel backend Rust:
binder::Binder::set_extension
Per ottenere un'estensione per un binder, utilizza le seguenti API:
- Nel backend NDK:
AIBinder_getExtension
- Nel backend Java:
android.os.IBinder.getExtension
- Nel backend CPP:
android::IBinder::getExtension
- Nel 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 HAL AIDL o le interfacce HAL AIDL, tieni presente le differenze rispetto alla scrittura di HAL HIDL.
- La sintassi del linguaggio AIDL è più simile a Java. La sintassi HIDL è simile a quella di C++.
- Tutte le interfacce AIDL hanno stati di errore incorporati. Invece di creare tipi di stato personalizzati, crea interi di stato costanti nei file di interfaccia e utilizza
EX_SERVICE_SPECIFIC
nei backend CPP/NDK eServiceSpecificException
nel backend Java. Consulta la sezione Gestione degli errori. - AIDL non avvia automaticamente i threadpool quando vengono inviati gli oggetti binder. Devono essere avviati manualmente (vedi Gestione dei thread).
- AIDL non viene interrotto in caso di errori di trasporto non controllati (HIDL
Return
si interrompe in caso di errori non controllati). - AIDL può dichiarare un solo tipo per file.
- Gli argomenti AIDL possono essere specificati come in/out/inout oltre al parametro output (non sono presenti "richiami sincroni").
- AIDL utilizza un fd come tipo primitivo anziché handle.
- HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per quelle compatibili. In AIDL, le modifiche compatibili con le versioni precedenti vengono eseguite in blocco.
AIDL non ha un concetto esplicito di versioni principali, ma 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
setInheritRt
deve essere utilizzata per ogni binder per attivare l'eredità della priorità in tempo reale.
Test della suite di test del fornitore (VTS) per gli HAL
Android si basa sulla Vendor Test Suite (VTS) per verificare le implementazioni HAL previste. Il VTS contribuisce a garantire che Android possa essere compatibile con le implementazioni precedenti dei fornitori. Le implementazioni che non superano il VTS presentano problemi di compatibilità noti che potrebbero impedirne il funzionamento con le future versioni del sistema operativo.
I VTS per gli HAL sono composti da due parti principali.
1. Verifica che gli HAL sul dispositivo siano noti e previsti da Android.
Questo insieme di test è disponibile in test/vts-testcase/hal/treble/vintf. Sono responsabili di verificare:
- Ogni interfaccia
@VintfStability
dichiarata in un manifest VINTF viene bloccata in una versione rilasciata nota. In questo modo, entrambi i lati dell'interfaccia concordano sulla definizione esatta della versione dell'interfaccia. Questo è necessario per il funzionamento di base. - Tutti gli HAL dichiarati in un manifest VINTF sono disponibili su quel 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.
- Tutti gli HAL dichiarati in un file manifest VINTF pubblicano la versione dell'interfaccia dichiarata nel manifest.
- Non sono presenti HAL ritirati su un dispositivo. Android non supporta più le versioni precedenti delle interfacce HAL, come descritto nel ciclo di vita di FCM.
- Sul dispositivo sono presenti gli HAL richiesti. Alcuni HAL sono obbligatori per il corretto funzionamento di Android.
2. Verifica il comportamento previsto di ogni HAL
Ogni interfaccia HAL ha i propri test VTS per verificare il comportamento previsto dei suoi 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.
Questi test tentano di coprire ogni aspetto dell'implementazione dell'HAL su cui si basa o potrebbe basarsi in futuro il framework Android.
Questi test includono la verifica del supporto delle funzionalità, la gestione degli errori e qualsiasi altro comportamento che un client potrebbe aspettarsi dal servizio.
Traguardi VTS per lo sviluppo HAL
I test VTS devono essere mantenuti aggiornati quando si creano o modificano 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 Android for Vendor. Devono essere pronti prima che le interfacce vengano bloccate in modo che gli sviluppatori possano creare le proprie implementazioni, verificarle e fornire feedback agli sviluppatori dell'interfaccia HAL.
VTS su Cuttlefish
Quando l'hardware non è disponibile, Android utilizza Cuttlefish come veicolo di sviluppo per le interfacce HAL. Ciò consente di eseguire test di verifica della validità e di integrazione scalabili di Android.
hal_implementation_test
verifica che Cuttlefish abbia implementazioni delle ultime versioni dell'interfaccia HAL per assicurarsi che Android sia pronto a gestire le nuove interfacce e che i test VTS siano pronti a testare le implementazioni dei nuovi fornitori non appena saranno disponibili nuovi hardware e dispositivi.