Android 11 には、HAL 用の AIDL を使用するための機能が導入されているため、Android の一部を HIDL なしで実装できます。可能であれば AIDL のみを使用するように HAL を変更してください(アップストリーム HAL で HIDL を使用する場合は、HIDL を使用する必要があります)。
フレームワーク コンポーネント(system.img
内のフレームワーク コンポーネントなど)とハードウェア コンポーネント(vendor.img
内のハードウェア コンポーネントなど)間の通信に AIDL を使用する HAL では、安定版 AIDL を使用する必要があります。ただし、パーティション内で通信する場合(HAL 間の通信など)、使用する IPC メカニズムに関する制限はありません。
モチベーション
AIDL は HIDL よりも古くから存在し、Android フレームワーク コンポーネント間やアプリ内など、さまざまな場面で使用されています。現在、AIDL は安定性もサポートされるようになっており、単一の IPC ランタイムでスタック全体を実装できます。また、AIDL のバージョニング システムは HIDL より優れています。AIDL の主な利点は次のとおりです。
- 単一の IPC 言語を使用するため、学習、デバッグ、最適化、保護の対象に必要なものも 1 つだけです。
- AIDL は、インターフェースのオーナー向けにインプレースのバージョニングをサポートしています。
- オーナーは、インターフェースの最後にメソッドを追加したり、Parcelable にフィールドを追加したりできます。つまり、コードのバージョニングを長年にわたって簡単に行うことができるうえ、そのコストを年々減らしていくことができます(型をインプレースで修正できるため、インターフェースのバージョンごとにライブラリを追加する必要がありません)。
- 拡張機能のインターフェースの接続は型システムで行われるのではなく、ランタイムに行われるため、ダウンストリームの拡張機能を新しいバージョンのインターフェースにリベースする必要はありません。
- 既存の AIDL インターフェースは、オーナーが固定することを選択した場合、直接使用できます。以前は、インターフェースのコピー全体を HIDL で作成する必要がありました。
AIDL ランタイムに対するビルド
AIDL には 3 つのバックエンド(Java、NDK、CPP)があります。安定版 AIDL を使用するには、常に system/lib*/libbinder.so
で libbinder
のシステムコピーを使用し、/dev/binder
で通信を行います。vendor
イメージのコードでは、(VNDK の)libbinder
を使用できないことを意味します(このライブラリは C++ API および内部動作が不安定なため)。その代わり、ネイティブ ベンダーコードで AIDL の NDK バックエンドを使用し、システムの libbinder.so
に支えられた libbinder_ndk
にリンクして、aidl_interface
エントリによって作成された NDK ライブラリにリンクする必要があります。正確なモジュール名については、モジュールの命名規則をご覧ください。
AIDL HAL インターフェースの作成
AIDL インターフェースをシステムとベンダー間で使用する場合、インターフェースで次の 2 点を変更する必要があります。
- すべての型定義に
@VintfStability
アノテーションを付ける必要があります。 aidl_interface
の宣言にstability: "vintf",
を含める必要があります。
これらの変更を行えるのはインターフェースのオーナーだけです。
これらの変更を行う場合、その変更を機能させるためには、インターフェースが VINTF マニフェスト内に存在している必要があります。これは(リリースされたインターフェースがフリーズしているかどうかの確認などの関連要件も同様)、VTS テスト vts_treble_vintf_vendor_test
を使用してテストしてください。これらの要件なしで @VintfStability
インターフェースを使用するには、別のプロセスに送信される前に、バインダ オブジェクトで NDK バックエンドの AIBinder_forceDowngradeToLocalStability
、C++ バックエンドの android::Stability::forceDowngradeToLocalStability
、Java バックエンドの android.os.Binder#forceDowngradeToSystemStability
のいずれかを呼び出します。
また、コードの移植性を最大限に高め、不要な追加ライブラリなどの潜在的な問題を回避するには、CPP バックエンドを無効にします。
3 つのバックエンド(Java、NDK、CPP)があるため、以下のコードサンプルでの backends
の使用は適切です。コードは、CPP バックエンドを無効にする方法を示しています。
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
AIDL HAL インターフェースを見つける
HAL 用の AOSP 安定版 AIDL インターフェースは、HIDL インターフェースと同じベース ディレクトリの aidl
フォルダ内にあります。
hardware/interfaces
: 一般的にハードウェアによって提供されるインターフェースの場合。frameworks/hardware/interfaces
: ハードウェアに提供される高レベルのインターフェースの場合。system/hardware/interfaces
: ハードウェアに提供される低レベルのインターフェースの場合。
拡張機能のインターフェースは vendor
または hardware
内の別の hardware/interfaces
サブディレクトリに配置します。
拡張機能のインターフェース
Android には、リリースごとに公式の AOSP インターフェースのセットがあります。Android パートナーがこれらのインターフェースに機能を追加しようとする場合、直接変更すべきではありません。これにより、Android ランタイムが AOSP Android ランタイムと互換性がなくなるためです。これらのインターフェースの変更を回避することで、GSI イメージは継続的に動作します。
拡張機能は次の 2 つの方法で登録できます。
- 実行時の場合は、接続済みの拡張機能のインターフェースをご覧ください
- スタンドアロンで、グローバルに VINTF に登録
ただし、拡張機能は登録されていますが、ベンダー固有(つまり、アップストリーム AOSP の一部ではない)のコンポーネントがインターフェースを使用する場合は、マージの競合の可能性はありません。ただし、アップストリームの AOSP コンポーネントに対してダウンストリームの変更を行うと、マージの競合が発生する可能性があります。そのため、次の戦略をおすすめします。
- インターフェースの追加を次のリリースで AOSP にアップストリームする。
- 柔軟性を高める(マージの競合がない)インターフェースの追加を次のリリースでアップストリームする。
Parcelable の拡張: ParcelableHolder
ParcelableHolder
は、Parcelable
の別のインスタンスを含めることのできる Parcelable
インターフェースのインスタンスです。
ParcelableHolder
の主なユースケースは、Parcelable
を拡張可能にすることです。たとえば、デバイス実装者が AOSP で定義されている Parcelable
、AospDefinedParcelable
を拡張して付加価値機能を含めることができると想定しているイメージがあります。
ParcelableHolder
インターフェースを使用して Parcelable
を拡張し、付加価値機能を含めます。ParcelableHolder
インターフェースには、Parcelable
のインスタンスが含まれています。Parcelable
に直接フィールドを追加しようとすると、以下のエラーが発生します。
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
前述のコードに示すように、この実装は機能しません。Android の次のリリースで Parcelable
が改訂される際に、デバイス実装者が追加したフィールドに競合が生じる可能性があるためです。
Parcelable のオーナーは、ParcelableHolder
を使用して Parcelable
のインスタンスに拡張ポイントを定義できます。
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
これにより、デバイス実装者は拡張機能用に独自の Parcelable
インスタンスを定義できます。
parcelable OemDefinedParcelable {
String x;
int[] y;
}
新しい Parcelable
インスタンスは ParcelableHolder
フィールドを使用して元の Parcelable
に追加できます。
// 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>();
AIDL HAL サーバーのインスタンス名
慣例により、AIDL HAL サービスのインスタンス名には $package.$type/$instance
という形式が使用されています。たとえば、vibrator HAL のインスタンスは android.hardware.vibrator.IVibrator/default
という名前で登録されます。
AIDL HAL サーバーの作成
@VintfStability
AIDL サーバーは VINTF マニフェストで宣言する必要があります。次に例を示します。
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
それ以外の場合は、AIDL サーバーは AIDL サービスを通常どおりに登録する必要があります。VTS テストの実行時には、宣言されているすべての AIDL HAL を使用できることが期待されます。
AIDL クライアントの作成
AIDL クライアントは互換性マトリックス内で自身を宣言する必要があります。次に例を示します。
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
既存の HAL を HIDL から AIDL に変換する
HIDL インターフェースを AIDL に変換するには、hidl2aidl
ツールを使用します。
hidl2aidl
機能
- 指定されたパッケージの HAL(
.hal
)ファイルに基づいて AIDL(.aidl
)ファイルを作成します。 - すべてのバックエンドを有効にして、新しく作成した AIDL パッケージのビルドルールを作成します。
- Java、CPP、NDK バックエンドで変換メソッドを作成し、HIDL 型から AIDL 型に変換します。
- 必要な依存関係を含む変換ライブラリのビルドルールを作成します。
- HIDL と AIDL の列挙子が CPP バックエンドと NDK バックエンドで同じ値になるように、静的アサートを作成します。
HAL ファイルのパッケージを AIDL ファイルに変換する手順は次のとおりです。
system/tools/hidl/hidl2aidl
にあるツールをビルドします。最新のソースからこのツールを構築することで、最適なエクスペリエンスを提供できます。最新バージョンを使用すると、以前のリリースの古いブランチのインターフェースを変換できます。
m hidl2aidl
出力ディレクトリの後に変換するパッケージを指定して、ツールを実行します。
必要に応じて、
-l
引数を使用して、新しいライセンス ファイルの内容を、生成されたすべてのファイルの先頭に追加します。正しいライセンスと日付を使用してください。hidl2aidl -o <output directory> -l <file with license> <package>
例:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
生成されたファイルを最初から最後まで確認し、変換によって生じた問題を修正します。
conversion.log
には、最初に修正する必要がある未処理の問題が含まれています。- 生成された AIDL ファイルに、対処が必要な警告と提案が含まれていることがあります。それらのコメントは
//
で始まります。 - パッケージをクリーンアップして改善します。
@JavaDerive
アノテーションで、toString
やequals
など、必要となる可能性のある機能を確認します。
必要なターゲットのみをビルドします。
- 使用しないバックエンドを無効にします。CPP バックエンドよりも NDK バックエンドを優先する場合は、AIDL ランタイムに対するビルドをご覧ください。
- 使用されない変換ライブラリまたは生成されたコードを削除します。
AIDL と HIDL の主な違いをご覧ください。
- AIDL の組み込みの
Status
と例外を使用すると、通常はインターフェースが改善され、別のインターフェース固有のステータス タイプが不要になります。 - メソッド内の AIDL インターフェース引数は、HIDL の場合と異なりデフォルトで
@nullable
ではありません。
- AIDL の組み込みの
AIDL HAL 用の SEPolicy
ベンダーコードから参照できる AIDL サービスタイプには、hal_service_type
属性が必要です。それ以外の場合、sepolicy の構成は他の AIDL サービスと同じです(ただし、HAL には特別な属性があります)。HAL サービス コンテキストの定義例を次に示します。
type hal_foo_service, service_manager_type, hal_service_type;
プラットフォームで定義されたサービスのほとんどで、正しいタイプのサービス コンテキストがすでに追加されています(たとえば、android.hardware.foo.IFoo/default
はすでに hal_foo_service
としてマークされています)。ただし、フレームワーク クライアントが複数のインスタンス名をサポートしている場合は、デバイス固有の service_contexts
ファイルにインスタンス名を追加する必要があります。
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
新しいタイプの HAL を作成する際は、HAL 属性を追加する必要があります。特定の HAL 属性は、複数のサービスタイプに関連付けられている場合があります(前述のとおり、各サービスには複数のインスタンスがあります)。HAL(foo
)の場合、hal_attribute(foo)
があります。このマクロは、属性 hal_foo_client
と hal_foo_server
を定義します。特定のドメインに対して、hal_client_domain
マクロと hal_server_domain
マクロを使用すると、ドメインが特定の HAL 属性に関連付けられます。たとえば、この HAL のクライアントであるシステム サーバーは、ポリシー hal_client_domain(system_server, hal_foo)
に対応します。同様に、HAL サーバーには hal_server_domain(my_hal_domain, hal_foo)
が含まれます。
通常、特定の HAL 属性に対して、参照用の HAL やサンプル HAL などの hal_foo_default
のようなドメインも作成します。ただし、一部のデバイスは独自のサーバー用にこれらのドメインを使用します。複数のサーバーのドメインの区別が重要となるのは、同じインターフェースを提供する複数のサーバーがあり、それぞれの実装に異なる権限セットが必要な場合のみです。いずれのマクロでも、hal_foo
は sepolicy オブジェクトではありません。このトークンは、クライアント サーバーペアに関連付けられた属性のグループを参照するためにこれらのマクロで使用されます。
ただし、この時点では hal_foo_service
と hal_foo
(hal_attribute(foo)
の属性ペア)は関連付けられていません。HAL 属性は、hal_attribute_service
マクロ(HIDL HAL は hal_attribute_hwservice
マクロを使用します)を使用して AIDL HAL サービスに関連付けられます。たとえば、hal_attribute_service(hal_foo, hal_foo_service)
が挙げられます。つまり、hal_foo_client
プロセスは HAL を保持し、hal_foo_server
プロセスは HAL を登録できます。これらの登録ルールの適用は、コンテキスト マネージャー(servicemanager
)によって行われます。
サービス名が HAL 属性に必ずしも対応しているとは限りません(例: hal_attribute_service(hal_foo, hal_foo2_service)
)。一般に、サービスが常に一緒に使用されることを意味するため、hal_foo2_service
を削除し、すべてのサービス コンテキストに hal_foo_service
を使用できます。HAL が複数の hal_attribute_service
インスタンスを設定する場合、HAL の元の属性名は一般的でないため変更できません。
これらすべてをまとめると、HAL の例は次のようになります。
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)
接続済みの拡張機能のインターフェース
拡張機能はバインダ インターフェースに接続できます。バインダ インターフェースは、サービス マネージャーに直接登録されているトップレベル インターフェースでも、サブインターフェースでもかまいません。拡張機能を取得する際、拡張機能のタイプが想定どおりであることを確認する必要があります。拡張機能は、バインダを提供するプロセスからのみ設定できます。
拡張機能が既存の HAL の機能を変更する場合は常に、接続済みの拡張機能を使用します。まったく新しい機能が必要な場合は、この方法を行う必要はありません。拡張機能のインターフェースをサービス マネージャーに直接登録できます。接続済みの拡張機能のインターフェースは、サブインターフェースに接続するのが最も有益です。これは、階層が深い場合や複数のインスタンスがある場合があるためです。グローバル拡張機能を使用して別のサービスのバインダ インターフェース階層をミラーリングする場合は、直接接続された拡張機能と同等の機能を提供するために、幅広いブックキーピングが必要になります。
バインダ上で拡張機能を設定するには、以下の API を使用します。
- NDK バックエンド:
AIBinder_setExtension
- Java バックエンド:
android.os.Binder.setExtension
- CPP バックエンド:
android::Binder::setExtension
- Rust バックエンド:
binder::Binder::set_extension
バインダ上で拡張機能を取得するには、以下の API を使用します。
- NDK バックエンド:
AIBinder_getExtension
- Java バックエンド:
android.os.IBinder.getExtension
- CPP バックエンド:
android::IBinder::getExtension
- Rust バックエンド:
binder::Binder::get_extension
上記の API について詳しくは、対応するバックエンドの getExtension
関数のドキュメントをご覧ください。拡張機能の使用方法の例は、hardware/interfaces/tests/extension/vibrator
にあります。
AIDL と HIDL の主な違い
AIDL HAL または AIDL HAL インターフェースを使用する際には、HIDL HAL と作成方法が異なることに注意してください。
- AIDL の構文は Java に似ており、HIDL の構文は C++ に似ています。
- すべての AIDL インターフェースにはエラー ステータスが組み込まれています。カスタムのステータス タイプを作成する代わりに、インターフェース ファイルに整数の定数ステータスを作成します。CPP および NDK バックエンドでは
EX_SERVICE_SPECIFIC
を使用し、Java バックエンドではServiceSpecificException
を使用します。エラー処理をご覧ください。 - AIDL は、バインダ オブジェクトの送信時に自動的にスレッドプールを開始するわけではありません。手動で開始する必要があります(スレッドの管理をご覧ください)。
- AIDL では未確認のトランスポート エラーが発生してもプロセスが中止されません(HIDL の
Return
では未確認のエラーが発生した場合、プロセスが中止されます)。 - AIDL では、ファイルごとに 1 つの型のみを宣言できます。
- AIDL の引数は、出力パラメータとして指定できるほか、
in
、out
、inout
のいずれかとしても指定できます(同期コールバックは行われません)。 - AIDL では、
fd
をhandle
ではなくプリミティブ型として使用します。 - HIDL では、互換性のない変更にはメジャー バージョンを使用し、互換性のある変更にはマイナー バージョンを使用します。AIDL では、下位互換性のある変更が行われます。AIDL にはメジャー バージョンの明示的な概念はなく、パッケージ名に組み込まれます。たとえば、AIDL ではパッケージ名
bluetooth2
が使用されることがあります。 - デフォルトでは、AIDL はリアルタイムの優先度を継承しません。リアルタイムの優先度の継承を有効にするには、バインダごとに
setInheritRt
関数を使用する必要があります。
HAL のベンダー テストスイート(VTS)テスト
Android では、ベンダー テストスイート(VTS)を使用して想定される HAL 実装を検証します。VTS により、Android が古いベンダー実装と下位互換性があることを確認できます。VTS に失敗した実装には、将来の OS バージョンでの機能を妨げる可能性のある既知の互換性の問題があります。
HAL の VTS は主に 2 つのパートからなります。
1. Android の既知のデバイスや想定されたデバイスで HAL を検証する
このテストセットは test/vts-testcase/hal/treble/vintf
にあります。このテストでは以下のことを検証します。
- VINTF マニフェストで宣言された
@VintfStability
インターフェースはすべて既知のリリース済みバージョンでフリーズされます。これにより、そのバージョンのインターフェースの同じ定義でインターフェースの両側が一致するようにします。これは、基本的なオペレーションで必要なものです。 - VINTF マニフェストで宣言された HAL はすべて、そのデバイスで利用可能です。宣言された HAL サービスを使用するのに十分なアクセス権限を持つすべてのクライアントがいつでもサービスを入手、利用できるようにする必要があります。
- VINTF マニフェストで宣言されたすべての HAL が、マニフェストで宣言されたバージョンのインターフェースでサービスを提供します。
- サポートが終了した HAL はデバイスで提供されません。FCM ライフサイクルに記載されているように、Android では HAL インターフェースの下位バージョンのサポートを終了します。
- 必須 HAL はデバイスにあります。一部の HAL は、Android が正常に機能するために必要です。
2. 各 HAL の想定される動作を検証する
各 HAL インターフェースは、独自の VTS テストでクライアントからの想定される動作を検証します。宣言された HAL インターフェースのすべてのインスタンスに対してテストケースを実行し、実装されているインターフェースのバージョンに基づく特定の動作を適用します。
Android フレームワークで使用される、または将来使用される可能性のある HAL 実装のすべてのアスペクトを網羅しようとします。
テストには、機能のサポート、エラー処理、クライアントがサービスに期待するその他の動作の検証を含みます。
HAL 開発用の VTS マイルストーン
Android の HAL インターフェースが作成または変更されるたびに、VTS テストを最新状態に保つことが期待されます。
Android Vendor API のリリースのためにフリーズされる前に VTS テストを完了し、ベンダー実装を検証できる状態にする必要があります。インターフェースがフリーズされる前に準備を整えることで、デベロッパーがそれぞれ実装を作成、検証し、HAL インターフェースのデベロッパーにフィードバックを提供できます。
Cuttlefish における VTS
ハードウェアが使用できない場合、Android では Cuttlefish を使って HAL インターフェースを開発します。これにより、Android のスケーラブルな VTS と統合テストが可能になります。
hal_implementation_test
では、Cuttlefish が最新バージョンの HAL インターフェースを実装していることをテストします。これにより、新しいハードウェアやデバイスが利用可能になり次第すぐに、Android が新しいインターフェースに対応できることと、新しいベンダー実装に対して VTS テストを実行できることを確認します。