AIDL untuk HAL

Android 11 memperkenalkan kemampuan menggunakan AIDL untuk HAL di Android. Hal ini memungkinkan untuk mengimplementasikan bagian-bagian Android tanpa HIDL. Transisi HAL untuk menggunakan AIDL secara eksklusif jika memungkinkan (bila HAL hulu menggunakan HIDL, HIDL harus digunakan).

HAL yang menggunakan AIDL untuk berkomunikasi antara komponen kerangka kerja, seperti yang ada di system.img , dan komponen perangkat keras, seperti yang ada di vendor.img , harus menggunakan AIDL Stabil. Namun, untuk berkomunikasi dalam suatu partisi, misalnya dari satu HAL ke HAL lainnya, tidak ada batasan penggunaan mekanisme IPC.

Motivasi

AIDL sudah ada lebih lama dibandingkan HIDL, dan digunakan di banyak tempat lain, seperti antar komponen framework Android atau dalam aplikasi. Kini AIDL memiliki dukungan stabilitas, sehingga dimungkinkan untuk mengimplementasikan seluruh tumpukan dengan satu runtime IPC. AIDL juga memiliki sistem versi yang lebih baik daripada HIDL.

  • Menggunakan satu bahasa IPC berarti hanya memiliki satu hal untuk dipelajari, di-debug, dioptimalkan, dan diamankan.
  • AIDL mendukung pembuatan versi di tempat untuk pemilik antarmuka:
    • Pemilik dapat menambahkan metode ke akhir antarmuka, atau bidang ke paket. Ini berarti lebih mudah untuk membuat versi kode selama bertahun-tahun, dan juga biaya dari tahun ke tahun lebih kecil (tipe dapat diubah di tempat dan tidak diperlukan perpustakaan tambahan untuk setiap versi antarmuka).
    • Antarmuka ekstensi dapat dipasang saat runtime, bukan pada sistem tipe, sehingga tidak perlu mengubah basis ekstensi downstream ke versi antarmuka yang lebih baru.
  • Antarmuka AIDL yang ada dapat digunakan secara langsung ketika pemiliknya memilih untuk menstabilkannya. Sebelumnya, seluruh salinan antarmuka harus dibuat di HIDL.

Menulis antarmuka AIDL HAL

Agar antarmuka AIDL dapat digunakan antara sistem dan vendor, antarmuka memerlukan dua perubahan:

  • Setiap definisi tipe harus dianotasi dengan @VintfStability .
  • Deklarasi aidl_interface perlu menyertakan stability: "vintf", .

Hanya pemilik antarmuka yang dapat melakukan perubahan ini.

Saat Anda melakukan perubahan ini, antarmuka harus ada dalam manifes VINTF agar dapat berfungsi. Uji ini (dan persyaratan terkait, seperti memverifikasi bahwa antarmuka yang dirilis dibekukan) menggunakan pengujian VTS vts_treble_vintf_vendor_test . Anda dapat menggunakan antarmuka @VintfStability tanpa persyaratan ini dengan memanggil AIBinder_forceDowngradeToLocalStability di backend NDK, android::Stability::forceDowngradeToLocalStability di backend C++, atau android.os.Binder#forceDowngradeToSystemStability di backend Java pada objek binder sebelum dikirim ke proses lain. Menurunkan versi layanan ke stabilitas vendor tidak didukung di Java karena semua aplikasi berjalan dalam konteks sistem.

Selain itu, untuk portabilitas kode maksimum dan untuk menghindari potensi masalah seperti perpustakaan tambahan yang tidak diperlukan, nonaktifkan backend CPP.

Perhatikan bahwa penggunaan backends pada contoh kode di bawah ini sudah benar, karena ada tiga backend (Java, NDK, dan CPP). Kode di bawah ini memberitahukan cara memilih backend CPP secara khusus, untuk menonaktifkannya.

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

Menemukan antarmuka AIDL HAL

Antarmuka AIDL Stabil AOSP untuk HAL berada di direktori dasar yang sama dengan antarmuka HIDL, dalam folder aidl .

  • perangkat keras/antarmuka
  • kerangka kerja/perangkat keras/antarmuka
  • sistem/perangkat keras/antarmuka

Anda harus meletakkan antarmuka ekstensi ke subdirektori hardware/interfaces lain di vendor atau hardware .

Antarmuka Ekstensi

Android memiliki serangkaian antarmuka AOSP resmi pada setiap rilis. Saat partner Android ingin menambahkan fungsionalitas ke antarmuka ini, mereka tidak boleh mengubahnya secara langsung karena ini berarti runtime Android mereka tidak kompatibel dengan runtime Android AOSP. Untuk perangkat GMS, menghindari perubahan antarmuka ini juga memastikan image GSI dapat terus berfungsi.

Ekstensi dapat didaftarkan dengan dua cara berbeda:

Bagaimanapun ekstensi didaftarkan, ketika komponen khusus vendor (artinya bukan bagian dari AOSP upstream) menggunakan antarmuka, tidak ada kemungkinan konflik penggabungan. Namun, ketika modifikasi hilir pada komponen AOSP hulu dilakukan, konflik penggabungan dapat terjadi, dan strategi berikut direkomendasikan:

  • penambahan antarmuka dapat di-upstream ke AOSP pada rilis berikutnya
  • penambahan antarmuka yang memungkinkan fleksibilitas lebih lanjut, tanpa konflik penggabungan, dapat di-upstream pada rilis berikutnya

Paket Ekstensi: ParcelableHolder

ParcelableHolder adalah Parcelable yang dapat berisi Parcelable lain. Kasus penggunaan utama ParcelableHolder adalah membuat Parcelable dapat diperluas. Misalnya, gambar yang diharapkan oleh pelaksana perangkat untuk dapat memperluas Parcelable yang ditentukan AOSP, AospDefinedParcelable , untuk menyertakan fitur nilai tambah.

Sebelumnya tanpa ParcelableHolder , pelaksana perangkat tidak dapat memodifikasi antarmuka AIDL stabil yang ditentukan AOSP karena akan terjadi kesalahan jika menambahkan lebih banyak kolom:

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

Seperti terlihat pada kode sebelumnya, praktik ini dihentikan karena kolom yang ditambahkan oleh pelaksana perangkat mungkin mengalami konflik saat Parcelable direvisi pada rilis Android berikutnya.

Dengan menggunakan ParcelableHolder , pemilik parcelable dapat menentukan titik ekstensi di Parcelable .

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

Kemudian pelaksana perangkat dapat menentukan Parcelable mereka sendiri untuk ekstensinya.

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

Terakhir, Parcelable baru dapat dilampirkan ke Parcelable asli melalui kolom 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>();

Membangun melawan runtime AIDL

AIDL memiliki tiga backend berbeda: Java, NDK, CPP. Untuk menggunakan AIDL Stabil, Anda harus selalu menggunakan salinan sistem libbinder di system/lib*/libbinder.so dan berbicara di /dev/binder . Untuk kode pada gambar vendor, ini berarti libbinder (dari VNDK) tidak dapat digunakan: perpustakaan ini memiliki C++ API yang tidak stabil dan internal yang tidak stabil. Sebaliknya, kode vendor asli harus menggunakan backend NDK AIDL, ditautkan ke libbinder_ndk (yang didukung oleh sistem libbinder.so ), dan ditautkan ke pustaka -ndk_platform yang dibuat oleh entri aidl_interface .

Nama instans server AIDL HAL

Berdasarkan konvensi, layanan AIDL HAL memiliki nama instance dengan format $package.$type/$instance . Misalnya, instance vibrator HAL didaftarkan sebagai android.hardware.vibrator.IVibrator/default .

Menulis server AIDL HAL

@VintfStability Server AIDL harus dideklarasikan dalam manifes VINTF, contohnya seperti ini:

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

Jika tidak, mereka harus mendaftarkan layanan AIDL secara normal. Saat menjalankan pengujian VTS, diharapkan semua AIDL HAL yang dinyatakan tersedia.

Menulis klien AIDL

Klien AIDL harus mendeklarasikan dirinya dalam matriks kompatibilitas, misalnya seperti ini:

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

Mengonversi HAL yang ada dari HIDL ke AIDL

Gunakan alat hidl2aidl untuk mengubah antarmuka HIDL menjadi AIDL.

fitur hidl2aidl :

  • Buat file .aidl berdasarkan file .hal untuk paket yang diberikan
  • Buat aturan build untuk paket AIDL yang baru dibuat dengan semua backend diaktifkan
  • Membuat metode terjemahan di backend Java, CPP, dan NDK untuk menerjemahkan dari tipe HIDL ke tipe AIDL
  • Buat aturan build untuk pustaka terjemahan dengan dependensi yang diperlukan
  • Buat pernyataan statis untuk memastikan bahwa enumerator HIDL dan AIDL memiliki nilai yang sama di backend CPP dan NDK

Ikuti langkah-langkah berikut untuk mengonversi paket file .hal ke file .aidl:

  1. Bangun alat yang terletak di system/tools/hidl/hidl2aidl .

    Membangun alat ini dari sumber terbaru memberikan pengalaman terlengkap. Anda dapat menggunakan versi terbaru untuk mengonversi antarmuka pada cabang lama dari rilis sebelumnya.

    m hidl2aidl
    
  2. Jalankan alat dengan direktori keluaran diikuti dengan paket yang akan dikonversi.

    Secara opsional, gunakan argumen -l untuk menambahkan konten file lisensi baru ke bagian atas semua file yang dihasilkan. Pastikan untuk menggunakan lisensi dan tanggal yang benar.

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

    Misalnya:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Baca seluruh file yang dihasilkan dan perbaiki masalah apa pun pada konversi.

    • conversion.log berisi masalah yang belum tertangani untuk diperbaiki terlebih dahulu.
    • File .aidl yang dihasilkan mungkin memiliki peringatan dan saran yang mungkin memerlukan tindakan. Komentar ini dimulai dengan // .
    • Manfaatkan kesempatan ini untuk membersihkan dan melakukan perbaikan pada paket.
    • Periksa anotasi @JavaDerive untuk fitur yang mungkin diperlukan, seperti toString atau equals .
  4. Bangun hanya target yang Anda perlukan.

    • Nonaktifkan backend yang tidak akan digunakan. Lebih memilih backend NDK daripada backend CPP, lihat memilih runtime .
    • Hapus pustaka terjemahan atau kode apa pun yang dihasilkannya yang tidak akan digunakan.
  5. Lihat perbedaan utama AIDL/HIDL .

    • Menggunakan Status dan pengecualian bawaan AIDL biasanya meningkatkan antarmuka dan menghilangkan kebutuhan akan jenis status khusus antarmuka lainnya.
    • Argumen antarmuka AIDL dalam metode tidak @nullable secara default seperti di HIDL.

Kebijakan untuk AIDL HAL

Jenis layanan AIDL yang terlihat oleh kode vendor harus memiliki atribut hal_service_type . Jika tidak, konfigurasi sepolicy sama dengan layanan AIDL lainnya (meskipun ada atribut khusus untuk HAL). Berikut adalah contoh definisi konteks layanan HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Untuk sebagian besar layanan yang ditentukan oleh platform, konteks layanan dengan jenis yang benar sudah ditambahkan (misalnya, android.hardware.foo.IFoo/default sudah ditandai sebagai hal_foo_service ). Namun, jika klien kerangka kerja mendukung beberapa nama instans, nama instans tambahan harus ditambahkan dalam file service_contexts khusus perangkat.

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

Atribut HAL harus ditambahkan ketika kita membuat HAL tipe baru. Atribut HAL tertentu mungkin dikaitkan dengan beberapa jenis layanan (masing-masing mungkin memiliki beberapa contoh seperti yang baru saja kita bahas). Untuk HAL, foo , kita punya hal_attribute(foo) . Makro ini mendefinisikan atribut hal_foo_client dan hal_foo_server . Untuk domain tertentu, makro hal_client_domain dan hal_server_domain mengaitkan domain dengan atribut HAL tertentu. Misalnya, server sistem yang menjadi klien HAL ini sesuai dengan kebijakan hal_client_domain(system_server, hal_foo) . Server HAL juga mencakup hal_server_domain(my_hal_domain, hal_foo) . Biasanya, untuk atribut HAL tertentu, kami juga membuat domain seperti hal_foo_default untuk referensi atau contoh HAL. Namun, beberapa perangkat menggunakan domain ini untuk servernya sendiri. Membedakan domain untuk beberapa server hanya penting jika kita memiliki beberapa server yang melayani antarmuka yang sama dan memerlukan izin berbeda dalam penerapannya. Di semua makro ini, hal_foo sebenarnya bukan objek sepolicy. Sebaliknya, token ini digunakan oleh makro ini untuk merujuk ke grup atribut yang terkait dengan pasangan server klien.

Namun, sejauh ini, kami belum mengaitkan hal_foo_service dan hal_foo (pasangan atribut dari hal_attribute(foo) ). Atribut HAL dikaitkan dengan layanan AIDL HAL menggunakan makro hal_attribute_service (HIDL HAL menggunakan makro hal_attribute_hwservice ). Misalnya, hal_attribute_service(hal_foo, hal_foo_service) . Ini berarti bahwa proses hal_foo_client dapat menguasai HAL, dan proses hal_foo_server dapat mendaftarkan HAL. Penegakan aturan pendaftaran ini dilakukan oleh manajer konteks ( servicemanager ). Perhatikan, nama layanan mungkin tidak selalu sesuai dengan atribut HAL. Misalnya, kita mungkin melihat hal_attribute_service(hal_foo, hal_foo2_service) . Namun secara umum, karena ini berarti layanan selalu digunakan bersama-sama, kami dapat mempertimbangkan untuk menghapus hal_foo2_service dan menggunakan hal_foo_service untuk semua konteks layanan kami. Kebanyakan HAL yang menetapkan beberapa hal_attribute_service karena nama atribut HAL asli tidak cukup umum dan tidak dapat diubah.

Jika digabungkan, contoh HAL terlihat seperti ini:

    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)

Antarmuka ekstensi terlampir

Ekstensi dapat dilampirkan ke antarmuka pengikat apa pun, baik itu antarmuka tingkat atas yang didaftarkan langsung dengan manajer layanan atau sub-antarmuka. Saat mendapatkan ekstensi, Anda harus memastikan jenis ekstensi tersebut sesuai yang diharapkan. Ekstensi hanya dapat diatur dari proses yang menyajikan pengikat.

Ekstensi terlampir harus digunakan setiap kali ekstensi mengubah fungsi HAL yang ada. Ketika fungsionalitas yang benar-benar baru diperlukan, mekanisme ini tidak perlu digunakan, dan antarmuka ekstensi dapat didaftarkan ke manajer layanan secara langsung. Antarmuka ekstensi yang terlampir paling masuk akal ketika dilampirkan ke sub-antarmuka, karena hierarki ini mungkin mendalam atau multi-instance. Menggunakan ekstensi global untuk mencerminkan hierarki antarmuka pengikat layanan lain akan memerlukan pembukuan ekstensif untuk menyediakan fungsionalitas yang setara dengan ekstensi yang dilampirkan secara langsung.

Untuk menyetel ekstensi pada binder, gunakan API berikut:

  • Di backend NDK: AIBinder_setExtension
  • Di backend Java: android.os.Binder.setExtension
  • Di backend CPP: android::Binder::setExtension
  • Di backend Rust: binder::Binder::set_extension

Untuk mendapatkan ekstensi pada binder, gunakan API berikut:

  • Di backend NDK: AIBinder_getExtension
  • Di backend Java: android.os.IBinder.getExtension
  • Di backend CPP: android::IBinder::getExtension
  • Di backend Rust: binder::Binder::get_extension

Anda dapat menemukan informasi selengkapnya untuk API ini di dokumentasi fungsi getExtension di backend yang sesuai. Contoh cara menggunakan ekstensi dapat ditemukan di hardware/interfaces/tests/extension/vibrator .

Perbedaan utama AIDL/HIDL

Saat menggunakan AIDL HAL atau menggunakan antarmuka AIDL HAL, perhatikan perbedaannya dibandingkan dengan menulis HIDL HAL.

  • Sintaks bahasa AIDL lebih mirip dengan Java. Sintaks HIDL mirip dengan C++.
  • Semua antarmuka AIDL memiliki status kesalahan bawaan. Daripada membuat tipe status khusus, buat int status konstan di file antarmuka dan gunakan EX_SERVICE_SPECIFIC di backend CPP/NDK dan ServiceSpecificException di backend Java. Lihat Penanganan Kesalahan .
  • AIDL tidak secara otomatis memulai threadpool ketika objek pengikat dikirim. Mereka harus dimulai secara manual (lihat manajemen thread ).
  • AIDL tidak membatalkan pada kesalahan transportasi yang tidak dicentang (HIDL Return dibatalkan pada kesalahan yang tidak dicentang).
  • AIDL hanya dapat mendeklarasikan satu jenis per file.
  • Argumen AIDL dapat ditentukan sebagai in/out/inout selain parameter output (tidak ada "panggilan balik sinkron").
  • AIDL menggunakan fd sebagai tipe primitif, bukan pegangan.
  • HIDL menggunakan versi mayor untuk perubahan yang tidak kompatibel dan versi minor untuk perubahan yang kompatibel. Di AIDL, perubahan yang kompatibel dengan versi sebelumnya dilakukan pada tempatnya. AIDL tidak memiliki konsep eksplisit tentang versi utama; sebaliknya, ini dimasukkan ke dalam nama paket. Misalnya, AIDL mungkin menggunakan nama paket bluetooth2 .
  • AIDL tidak mewarisi prioritas waktu nyata secara default. Fungsi setInheritRt harus digunakan per pengikat untuk mengaktifkan pewarisan prioritas waktu nyata.