HAL 用の AIDL

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 より優れています。

  • 単一の IPC 言語を使用するため、学習、デバッグ、最適化、保護の対象に必要なものも 1 つだけです。
  • AIDL は、インターフェースのオーナー向けにインプレースのバージョニングをサポートしています。
    • オーナーは、インターフェースの最後にメソッドを追加したり、Parcelable にフィールドを追加したりできます。 つまり、コードのバージョニングを長年にわたって簡単に行うことができるうえ、そのコストを年々減らしていくことができます(型をインプレースで修正できるため、インターフェースのバージョンごとにライブラリを追加する必要がありません)。
    • 拡張機能のインターフェースの接続は型システムで行われるのではなく、ランタイムに行われるため、ダウンストリームの拡張機能を新しいバージョンのインターフェースにリベースする必要はありません。
  • 既存の AIDL インターフェースは、オーナーが固定することを選択した場合、直接使用できます。以前は、インターフェースのコピー全体を HIDL で作成する必要がありました。

AIDL ランタイムに対するビルド

AIDL には 3 つのバックエンド(Java、NDK、CPP)があります。安定版 AIDL を使用するには、常に libbinder(system/lib*/libbinder.so)のシステムコピーを使用し、/dev/binder で通信を行う必要があります。これは、ベンダー イメージのコードでは(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 のいずれかを呼び出します。Java では、すべてのアプリがシステム コンテキストで実行されるため、サービスをベンダーの安定性のためにダウングレードすることはサポートされていません。

また、コードの移植性を最大限に高め、不要な追加ライブラリなどの潜在的な問題を回避するには、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 ランタイムと互換性がないためです。GMS デバイスでは、これらのインターフェースの変更を回避することで、GSI イメージの継続的な動作を保証します。

拡張機能は次の 2 つの方法で登録できます。

ただし、拡張機能は登録されていますが、ベンダー固有(つまり、アップストリーム AOSP の一部ではない)のコンポーネントがインターフェースを使用する場合は、マージの競合の可能性はありません。ただし、アップストリームの AOSP コンポーネントに対してダウンストリームの変更を行うと、マージの競合が発生する可能性があります。そのため、次の戦略をおすすめします。

  • インターフェースの追加を次のリリースで AOSP にアップストリームする
  • マージの競合がない柔軟性を高めるインターフェースの追加を次のリリースでアップストリームする

Parcelable の拡張: ParcelableHolder

ParcelableHolderParcelable であり、別の Parcelable を含めることができます。ParcelableHolder の主なユースケースは、Parcelable を拡張可能にすることです。たとえば、デバイス実装者が AOSP で定義されている ParcelableAospDefinedParcelable を拡張して付加価値機能を含めることができると想定しているイメージがあります。

以前は ParcelableHolder がなければ、デバイス実装者は AOSP で定義されている安定版 AIDL インターフェースを変更できませんでした。これは、フィールドを追加する際にエラーが発生するためです。

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 ファイルに基づいて .aidl ファイルを作成します。
  • すべてのバックエンドを有効にして、新しく作成した AIDL パッケージのビルドルールを作成します。
  • Java、CPP、NDK バックエンドで変換メソッドを作成し、HIDL 型から AIDL 型に変換します。
  • 必要な依存関係を含む変換ライブラリのビルドルールを作成します。
  • HIDL と AIDL の列挙子が CPP バックエンドと NDK バックエンドで同じ値になるように、静的アサートを作成します。

.hal ファイルのパッケージを .aidl ファイルに変換する手順は次のとおりです。

  1. system/tools/hidl/hidl2aidl にあるツールをビルドします。

    最新のソースからこのツールを構築することで、最適なエクスペリエンスを提供できます。最新バージョンを使用すると、以前のリリースの古いブランチのインターフェースを変換できます。

    m hidl2aidl
  2. 出力ディレクトリの後に変換するパッケージを指定して、ツールを実行します。

    必要に応じて、-l 引数を使用して、新しいライセンス ファイルの内容を、生成されたすべてのファイルの先頭に追加します。正しいライセンスと日付を使用してください。

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

    次に例を示します。

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. 生成されたファイルを最初から最後まで確認し、変換によって生じた問題を修正します。

    • conversion.log には、最初に修正する必要がある未処理の問題が含まれています。
    • 生成された .aidl ファイルに、対処が必要な警告と提案が含まれていることがあります。それらのコメントは // で始まります。
    • 機会があればパッケージをクリーンアップして改善します。
    • @JavaDerive アノテーションで、toStringequals など、必要となる可能性のある機能を確認します。
  4. 必要なターゲットのみをビルドします。

    • 使用しないバックエンドを無効にします。CPP バックエンドよりも NDK バックエンドを優先する場合は、ランタイムの選択をご覧ください。
    • 使用されない変換ライブラリまたは生成されたコードを削除します。
  5. AIDL と HIDL の主な違いをご覧ください。

    • AIDL の組み込みの Status と例外を使用すると、通常はインターフェースが改善され、別のインターフェース固有のステータス タイプが不要になります。
    • メソッド内の AIDL インターフェース引数は、HIDL の場合と異なりデフォルトで @nullable ではありません。

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_clienthal_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_servicehal_foohal_attribute(foo) の属性ペア)は関連付けられていません。HAL 属性は、hal_attribute_service マクロを使用して AIDL HAL サービスに関連付けられます(HIDL HAL は hal_attribute_hwservice マクロを使用します)。たとえば、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_attribute_service が設定されているほとんどの HAL は、元の 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 の引数は、出力パラメータとして指定できるほか、入力 / 出力 / 入出力として指定することもできます(「同期コールバック」は行われません)。
  • AIDL では、fd をハンドルではなくプリミティブ型として使用します。
  • 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 テストを実行できることを確認します。