AIDL per gli HAL

Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android. Ciò consente di implementare parti di Android senza HIDL. HAL di transizione per utilizzare esclusivamente AIDL ove possibile (quando gli HAL upstream utilizzano HIDL, è necessario utilizzare 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 Stable AIDL. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non vi è alcuna restrizione 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 dispone del supporto per la stabilità, è possibile implementare un intero stack con un singolo runtime IPC. AIDL ha anche un sistema di controllo delle versioni migliore di HIDL.

  • Usare un unico linguaggio IPC significa avere solo una cosa da imparare, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo delle versioni sul posto per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile eseguire la versione del codice nel corso degli anni e anche il costo anno dopo anno è inferiore (i tipi possono essere modificati sul posto e non sono necessarie librerie aggiuntive per ciascuna versione dell'interfaccia).
    • Le interfacce di estensione possono essere collegate in fase di esecuzione piuttosto che nel sistema di tipi, quindi non è necessario ribasare le estensioni downstream su versioni più recenti delle interfacce.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il suo proprietario sceglie di stabilizzarla. Prima, un'intera copia dell'interfaccia doveva essere creata in HIDL.

Scrivere un'interfaccia AIDL HAL

Per utilizzare un'interfaccia AIDL tra il sistema e il fornitore, l'interfaccia richiede due modifiche:

  • Ogni definizione di tipo deve essere annotata con @VintfStability .
  • La dichiarazione aidl_interface deve includere stability: "vintf", .

Solo il proprietario di un'interfaccia può apportare queste modifiche.

Quando apporti queste modifiche, l'interfaccia deve trovarsi nel manifest VINTF per funzionare. Prova questo (e i requisiti correlati, come verificare 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 ad 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, disabilitare il backend CPP.

Si noti che l'uso dei backends nell'esempio di codice riportato di seguito è corretto, in quanto vi sono tre backend (Java, NDK e CPP). Il codice seguente spiega come selezionare specificamente il backend CPP, per disabilitarlo.

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

Trovare le interfacce AIDL HAL

AOSP Stable Le interfacce AIDL per gli HAL si trovano nelle stesse directory di base delle interfacce HIDL, nelle cartelle aidl .

  • hardware/interfacce
  • framework/hardware/interfacce
  • sistema/hardware/interfacce

Dovresti inserire le interfacce di estensione in altre sottodirectory hardware/interfaces in vendor o hardware .

Interfacce di estensione

Android ha una serie di interfacce AOSP ufficiali con ogni versione. Quando i partner Android desiderano aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android non è compatibile con il runtime Android AOSP. Per i dispositivi GMS, evitare di modificare queste interfacce è anche ciò che garantisce che l'immagine GSI possa continuare a funzionare.

Le estensioni possono registrarsi in due modi diversi:

  • in fase di esecuzione, vedere le estensioni allegate .
  • autonomo, registrato a livello globale e in VINTF.

Tuttavia, un'estensione viene registrata, quando i componenti specifici del fornitore (ovvero non una parte di AOSP upstream) utilizzano l'interfaccia, non vi è alcuna possibilità di conflitto di unione. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:

  • le aggiunte all'interfaccia possono essere caricate su AOSP nella prossima versione
  • le aggiunte di interfaccia che consentono ulteriore flessibilità, senza conflitti di unione, possono essere integrate nella prossima versione

Estensione Parcelables: 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 di dispositivi si aspettino di poter estendere un Parcelable definito da AOSP, AospDefinedParcelable , per includere le loro funzionalità a valore aggiunto.

In precedenza senza ParcelableHolder , gli implementatori del dispositivo non potevano modificare un'interfaccia AIDL stabile definita da AOSP perché sarebbe un errore aggiungere più campi:

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

Come visto nel codice precedente, questa pratica è interrotta perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando il Parcelable viene rivisto nelle prossime versioni 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;
}

Quindi gli implementatori del dispositivo possono definire il proprio Parcelable per la loro estensione.

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

Infine, il nuovo Parcelable può essere collegato al Parcelable originale tramite 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>();

Creazione rispetto al runtime AIDL

AIDL ha tre diversi backend: Java, NDK, CPP. Per usare Stable AIDL, devi sempre usare la copia di sistema di libbinder in system/lib*/libbinder.so e parlare su /dev/binder . Per il codice sull'immagine del fornitore, ciò significa che libbinder (dal VNDK) non può essere utilizzato: questa libreria ha un'API C++ instabile e componenti interni instabili. Invece, il codice del fornitore nativo deve utilizzare il backend NDK di AIDL, collegarsi a libbinder_ndk (che è supportato dal sistema libbinder.so ) e collegarsi alle librerie -ndk_platform create dalle voci aidl_interface .

Nomi delle istanze del server AIDL HAL

Per convenzione, i servizi AIDL HAL hanno un nome di istanza nel formato $package.$type/$instance . Ad esempio, un'istanza del vibratore HAL è registrata come android.hardware.vibrator.IVibrator/default .

Scrivere un server AIDL HAL

@VintfStability server AIDL devono essere dichiarati nel manifest VINTF, ad esempio in questo modo:

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

Altrimenti, dovrebbero registrare normalmente un servizio AIDL. Quando si eseguono i test VTS, è previsto che siano disponibili tutti gli HAL AIDL dichiarati.

Scrivere un client AIDL

I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio in questo modo:

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

Conversione di un HAL esistente da HIDL a AIDL

Utilizzare lo strumento hidl2aidl per convertire un'interfaccia HIDL in AIDL.

Caratteristiche di hidl2aidl :

  • Crea file .aidl basati sui file .hal per il pacchetto specificato
  • Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend abilitati
  • Creare metodi translate nei backend Java, CPP e NDK per tradurre dai tipi HIDL ai tipi AIDL
  • Crea regole di compilazione 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

Segui questi passaggi per convertire un pacchetto di file .hal in file .aidl:

  1. Crea lo strumento che si trova in system/tools/hidl/hidl2aidl .

    La creazione di questo strumento dall'ultima fonte fornisce l'esperienza più completa. È possibile utilizzare l'ultima versione per convertire le interfacce sui rami meno recenti delle versioni precedenti.

    m hidl2aidl
    
  2. Eseguire lo strumento con una directory di output seguita dal pacchetto da convertire.

    Facoltativamente, utilizzare l'argomento -l per aggiungere il contenuto 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>
    

    Per esempio:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Leggi i file generati e risolvi eventuali problemi con la conversione.

    • conversion.log contiene tutti i problemi non gestiti da risolvere prima.
    • I file .aidl generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un'azione. Questi commenti iniziano con // .
    • Cogli l'occasione per ripulire e apportare miglioramenti al pacchetto.
    • Controlla l'annotazione @JavaDerive per le funzionalità che potrebbero essere necessarie, ad esempio toString o equals .
  4. Costruisci solo gli obiettivi di cui hai bisogno.

    • Disabilita i backend che non verranno utilizzati. Preferisci il backend NDK al backend CPP, vedi la scelta del runtime .
    • Rimuovi le librerie di traduzione o qualsiasi parte del loro codice generato che non verrà utilizzato.
  5. Vedere Principali differenze tra AIDL e HIDL .

    • L'utilizzo dello Status integrato di AIDL e delle eccezioni 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 @nullable per impostazione predefinita come lo erano in HIDL.

Sepolicy per AIDL HAL

Un tipo di servizio AIDL visibile al codice fornitore deve avere l'attributo hal_service_type . Altrimenti, la configurazione di sepolicy è la stessa di qualsiasi altro servizio AIDL (sebbene ci siano attributi speciali per gli HAL). Ecco una definizione di esempio 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 sarebbe già contrassegnato come hal_foo_service ). Tuttavia, se un client framework supporta più nomi di istanza, è necessario aggiungere altri nomi di istanza 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 creiamo un nuovo tipo di HAL. Un attributo HAL specifico potrebbe essere associato a più tipi di servizio (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 alla politica hal_client_domain(system_server, hal_foo) . Un server HAL include allo stesso modo hal_server_domain(my_hal_domain, hal_foo) . Tipicamente, per un dato attributo HAL, creiamo anche un dominio come hal_foo_default per HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. La distinzione tra domini per più server è importante solo se disponiamo di più server che servono la stessa interfaccia e necessitano di un set di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è effettivamente un oggetto sepolicy. Invece, questo token viene utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia client-server.

Tuttavia, finora non abbiamo associato hal_foo_service e hal_foo (la coppia di attributi da hal_attribute(foo) ). Un attributo HAL è associato ai servizi HAL AIDL utilizzando la macro hal_attribute_service (gli 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 impossessarsi dell'HAL e i processi hal_foo_server possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore di contesto ( servicemanager ). Si noti 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 generale, tuttavia, poiché ciò 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 nostri contesti di servizio. La maggior parte degli HAL che impostano più hal_attribute_service sono dovuti al fatto che il nome dell'attributo HAL originale non è abbastanza generale e non può essere modificato.

Mettendo tutto insieme, un esempio di HAL ha questo 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 allegate

Un'estensione può essere collegata a qualsiasi interfaccia di binder, sia che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ottieni un'estensione, devi confermare che il tipo di estensione è quello previsto. Le estensioni possono essere impostate solo dal processo che serve un raccoglitore.

Le estensioni allegate dovrebbero essere utilizzate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, questo meccanismo non deve essere utilizzato e un'interfaccia di estensione può essere registrata direttamente con il gestore del servizio. Le interfacce di estensione collegate hanno più senso quando sono collegate a interfacce secondarie, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia del raccoglitore di un altro servizio richiederebbe un'ampia contabilità per fornire funzionalità equivalenti alle estensioni collegate direttamente.

Per impostare un'estensione sul raccoglitore, utilizza le seguenti API:

  • Nel back-end NDK: AIBinder_setExtension
  • Nel backend Java: android.os.Binder.setExtension
  • Nel backend CPP: android::Binder::setExtension

Per ottenere un'estensione su un raccoglitore, utilizza le seguenti API:

  • Nel back-end NDK: AIBinder_getExtension
  • Nel backend Java: android.os.IBinder.getExtension
  • Nel backend CPP: android::IBinder::getExtension

Puoi trovare ulteriori informazioni per queste API nella documentazione della funzione getExtension nel backend corrispondente. Un esempio di come utilizzare le estensioni può essere trovato in hardware/interfaces/tests/extension/vibrator .

Principali differenze tra AIDL e HIDL

Quando si utilizzano HAL AIDL o si utilizzano interfacce HAL AIDL, tenere presente le differenze rispetto alla scrittura di HAL HIDL.

  • La sintassi del linguaggio AIDL è più vicina a Java. La sintassi HIDL è simile a C++.
  • Tutte le interfacce AIDL hanno stati di errore incorporati. Invece di creare tipi di stato personalizzati, crea int di stato costanti nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP/NDK e ServiceSpecificException nel backend Java. Vedere Gestione degli errori .
  • AIDL non avvia automaticamente i threadpool quando vengono inviati oggetti binder. Devono essere avviati manualmente (vedi gestione dei thread ).
  • AIDL non si interrompe in caso di errori di trasporto non controllati (HIDL Return si interrompe in caso di errori non controllati).
  • AIDL può dichiarare solo un tipo per file.
  • Gli argomenti AIDL possono essere specificati come in/out/inout oltre al parametro di output (non ci sono "callback sincroni").
  • AIDL usa un fd come tipo primitivo invece di 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 eseguite sul posto. AIDL non ha un concetto esplicito di versioni principali; invece, 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 raccoglitore per abilitare l'ereditarietà della priorità in tempo reale.
,

Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android. Ciò consente di implementare parti di Android senza HIDL. HAL di transizione per utilizzare esclusivamente AIDL ove possibile (quando gli HAL upstream utilizzano HIDL, è necessario utilizzare 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 Stable AIDL. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non vi è alcuna restrizione 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 dispone del supporto per la stabilità, è possibile implementare un intero stack con un singolo runtime IPC. AIDL ha anche un sistema di controllo delle versioni migliore di HIDL.

  • Usare un unico linguaggio IPC significa avere solo una cosa da imparare, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo delle versioni sul posto per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile eseguire la versione del codice nel corso degli anni e anche il costo anno dopo anno è inferiore (i tipi possono essere modificati sul posto e non sono necessarie librerie aggiuntive per ciascuna versione dell'interfaccia).
    • Le interfacce di estensione possono essere collegate in fase di esecuzione piuttosto che nel sistema di tipi, quindi non è necessario ribasare le estensioni downstream su versioni più recenti delle interfacce.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il suo proprietario sceglie di stabilizzarla. Prima, un'intera copia dell'interfaccia doveva essere creata in HIDL.

Scrivere un'interfaccia AIDL HAL

Per utilizzare un'interfaccia AIDL tra il sistema e il fornitore, l'interfaccia richiede due modifiche:

  • Ogni definizione di tipo deve essere annotata con @VintfStability .
  • La dichiarazione aidl_interface deve includere stability: "vintf", .

Solo il proprietario di un'interfaccia può apportare queste modifiche.

Quando apporti queste modifiche, l'interfaccia deve trovarsi nel manifest VINTF per funzionare. Prova questo (e i requisiti correlati, come verificare 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 ad 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, disabilitare il backend CPP.

Si noti che l'uso dei backends nell'esempio di codice riportato di seguito è corretto, in quanto vi sono tre backend (Java, NDK e CPP). Il codice seguente spiega come selezionare specificamente il backend CPP, per disabilitarlo.

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

Trovare le interfacce AIDL HAL

AOSP Stable Le interfacce AIDL per gli HAL si trovano nelle stesse directory di base delle interfacce HIDL, nelle cartelle aidl .

  • hardware/interfacce
  • framework/hardware/interfacce
  • sistema/hardware/interfacce

Dovresti inserire le interfacce di estensione in altre sottodirectory hardware/interfaces in vendor o hardware .

Interfacce di estensione

Android ha una serie di interfacce AOSP ufficiali con ogni versione. Quando i partner Android desiderano aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android non è compatibile con il runtime Android AOSP. Per i dispositivi GMS, evitare di modificare queste interfacce è anche ciò che garantisce che l'immagine GSI possa continuare a funzionare.

Le estensioni possono registrarsi in due modi diversi:

  • in fase di esecuzione, vedere le estensioni allegate .
  • autonomo, registrato a livello globale e in VINTF.

Tuttavia, un'estensione viene registrata, quando i componenti specifici del fornitore (ovvero non una parte di AOSP upstream) utilizzano l'interfaccia, non vi è alcuna possibilità di conflitto di unione. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:

  • le aggiunte all'interfaccia possono essere caricate su AOSP nella prossima versione
  • le aggiunte di interfaccia che consentono ulteriore flessibilità, senza conflitti di unione, possono essere integrate nella prossima versione

Estensione Parcelables: 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 di dispositivi si aspettino di poter estendere un Parcelable definito da AOSP, AospDefinedParcelable , per includere le loro funzionalità a valore aggiunto.

In precedenza senza ParcelableHolder , gli implementatori del dispositivo non potevano modificare un'interfaccia AIDL stabile definita da AOSP perché sarebbe un errore aggiungere più campi:

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

Come visto nel codice precedente, questa pratica è interrotta perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando il Parcelable viene rivisto nelle prossime versioni 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;
}

Quindi gli implementatori del dispositivo possono definire il proprio Parcelable per la propria estensione.

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

Infine, il nuovo Parcelable può essere collegato al Parcelable originale tramite 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>();

Creazione rispetto al runtime AIDL

AIDL ha tre diversi backend: Java, NDK, CPP. Per usare Stable AIDL, devi sempre usare la copia di sistema di libbinder in system/lib*/libbinder.so e parlare su /dev/binder . Per il codice sull'immagine del fornitore, ciò significa che libbinder (dal VNDK) non può essere utilizzato: questa libreria ha un'API C++ instabile e componenti interni instabili. Invece, il codice del fornitore nativo deve utilizzare il backend NDK di AIDL, collegarsi a libbinder_ndk (che è supportato dal sistema libbinder.so ) e collegarsi alle librerie -ndk_platform create dalle voci aidl_interface .

Nomi delle istanze del server AIDL HAL

Per convenzione, i servizi AIDL HAL hanno un nome di istanza nel formato $package.$type/$instance . Ad esempio, un'istanza del vibratore HAL è registrata come android.hardware.vibrator.IVibrator/default .

Scrivere un server AIDL HAL

@VintfStability server AIDL devono essere dichiarati nel manifest VINTF, ad esempio in questo modo:

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

Altrimenti, dovrebbero registrare normalmente un servizio AIDL. Quando si eseguono i test VTS, è previsto che siano disponibili tutti gli HAL AIDL dichiarati.

Scrivere un client AIDL

I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio in questo modo:

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

Conversione di un HAL esistente da HIDL a AIDL

Utilizzare lo strumento hidl2aidl per convertire un'interfaccia HIDL in AIDL.

Caratteristiche di hidl2aidl :

  • Crea file .aidl basati sui file .hal per il pacchetto specificato
  • Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend abilitati
  • Creare metodi translate nei backend Java, CPP e NDK per tradurre dai tipi HIDL ai tipi AIDL
  • Crea regole di compilazione 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

Segui questi passaggi per convertire un pacchetto di file .hal in file .aidl:

  1. Crea lo strumento che si trova in system/tools/hidl/hidl2aidl .

    La creazione di questo strumento dall'ultima fonte fornisce l'esperienza più completa. È possibile utilizzare l'ultima versione per convertire le interfacce sui rami meno recenti delle versioni precedenti.

    m hidl2aidl
    
  2. Eseguire lo strumento con una directory di output seguita dal pacchetto da convertire.

    Facoltativamente, utilizzare l'argomento -l per aggiungere il contenuto 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>
    

    Per esempio:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Leggi i file generati e risolvi eventuali problemi con la conversione.

    • conversion.log contiene tutti i problemi non gestiti da risolvere prima.
    • I file .aidl generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un'azione. Questi commenti iniziano con // .
    • Cogli l'occasione per ripulire e apportare miglioramenti al pacchetto.
    • Controlla l'annotazione @JavaDerive per le funzionalità che potrebbero essere necessarie, ad esempio toString o equals .
  4. Costruisci solo gli obiettivi di cui hai bisogno.

    • Disabilita i backend che non verranno utilizzati. Preferisci il backend NDK al backend CPP, vedi la scelta del runtime .
    • Rimuovi le librerie di traduzione o qualsiasi parte del loro codice generato che non verrà utilizzato.
  5. Vedere Principali differenze tra AIDL e HIDL .

    • L'utilizzo dello Status integrato di AIDL e delle eccezioni 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 @nullable per impostazione predefinita come lo erano in HIDL.

Sepolicy per AIDL HAL

Un tipo di servizio AIDL visibile al codice fornitore deve avere l'attributo hal_service_type . Altrimenti, la configurazione di sepolicy è la stessa di qualsiasi altro servizio AIDL (sebbene ci siano attributi speciali per gli HAL). Ecco una definizione di esempio 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 sarebbe già contrassegnato come hal_foo_service ). Tuttavia, se un client framework supporta più nomi di istanza, è necessario aggiungere altri nomi di istanza 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 creiamo un nuovo tipo di HAL. Un attributo HAL specifico potrebbe essere associato a più tipi di servizio (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 alla politica hal_client_domain(system_server, hal_foo) . Un server HAL include allo stesso modo hal_server_domain(my_hal_domain, hal_foo) . Tipicamente, per un dato attributo HAL, creiamo anche un dominio come hal_foo_default per HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. La distinzione tra domini per più server è importante solo se disponiamo di più server che servono la stessa interfaccia e necessitano di un set di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è effettivamente un oggetto sepolicy. Invece, questo token viene utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia client-server.

Tuttavia, finora non abbiamo associato hal_foo_service e hal_foo (la coppia di attributi da hal_attribute(foo) ). Un attributo HAL è associato ai servizi HAL AIDL utilizzando la macro hal_attribute_service (gli 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 impossessarsi dell'HAL e i processi hal_foo_server possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore di contesto ( servicemanager ). Si noti 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 generale, tuttavia, poiché ciò 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 nostri contesti di servizio. La maggior parte degli HAL che impostano più hal_attribute_service sono dovuti al fatto che il nome dell'attributo HAL originale non è abbastanza generale e non può essere modificato.

Mettendo tutto insieme, un esempio di HAL ha questo 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 allegate

Un'estensione può essere collegata a qualsiasi interfaccia di binder, sia che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ottieni un'estensione, devi confermare che il tipo di estensione è quello previsto. Le estensioni possono essere impostate solo dal processo che serve un raccoglitore.

Le estensioni allegate dovrebbero essere utilizzate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, questo meccanismo non deve essere utilizzato e un'interfaccia di estensione può essere registrata direttamente con il gestore del servizio. Le interfacce di estensione collegate hanno più senso quando sono collegate a interfacce secondarie, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia del raccoglitore di un altro servizio richiederebbe un'ampia contabilità per fornire funzionalità equivalenti alle estensioni collegate direttamente.

Per impostare un'estensione sul raccoglitore, utilizza le seguenti API:

  • Nel back-end NDK: AIBinder_setExtension
  • Nel backend Java: android.os.Binder.setExtension
  • Nel backend CPP: android::Binder::setExtension

Per ottenere un'estensione su un raccoglitore, utilizza le seguenti API:

  • Nel back-end NDK: AIBinder_getExtension
  • Nel backend Java: android.os.IBinder.getExtension
  • Nel backend CPP: android::IBinder::getExtension

Puoi trovare ulteriori informazioni per queste API nella documentazione della funzione getExtension nel backend corrispondente. Un esempio di come utilizzare le estensioni può essere trovato in hardware/interfaces/tests/extension/vibrator .

Principali differenze tra AIDL e HIDL

Quando si utilizzano HAL AIDL o si utilizzano interfacce HAL AIDL, tenere presente le differenze rispetto alla scrittura di HAL HIDL.

  • La sintassi del linguaggio AIDL è più vicina a Java. La sintassi HIDL è simile a C++.
  • Tutte le interfacce AIDL hanno stati di errore incorporati. Invece di creare tipi di stato personalizzati, crea int di stato costanti nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP/NDK e ServiceSpecificException nel backend Java. Vedere Gestione degli errori .
  • AIDL non avvia automaticamente i threadpool quando vengono inviati oggetti binder. Devono essere avviati manualmente (vedi gestione dei thread ).
  • AIDL non si interrompe in caso di errori di trasporto non controllati (HIDL Return si interrompe in caso di errori non controllati).
  • AIDL può dichiarare solo un tipo per file.
  • Gli argomenti AIDL possono essere specificati come in/out/inout oltre al parametro di output (non ci sono "callback sincroni").
  • AIDL usa un fd come tipo primitivo invece di 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 eseguite sul posto. AIDL non ha un concetto esplicito di versioni principali; invece, 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 raccoglitore per abilitare l'ereditarietà della priorità in tempo reale.