AIDL for HALs

Android 11 introduces the ability to use AIDL for HALs in Android. This makes it possible to implement parts of Android without HIDL. Transition HALs to use AIDL exclusively where possible (when upstream HALs use HIDL, HIDL must be used).

HALs using AIDL to communicate between framework components, such as those in system.img, and hardware components, such as those in vendor.img, must use Stable AIDL. However, to communicate within a partition, for instance from one HAL to another, there's no restriction on the IPC mechanism to use.

Motivation

AIDL has been around longer than HIDL, and is used in many other places, such as between Android framework components or in apps. Now that AIDL has stability support, it's possible to implement an entire stack with a single IPC runtime. AIDL also has a better versioning system than HIDL.

  • Using a single IPC language means having only one thing to learn, debug, optimize, and secure.
  • AIDL supports in-place versioning for the owners of an interface:
    • Owners can add methods to the end of interfaces, or fields to parcelables. This means it's easier to version code over the years, and also the year over year cost is smaller (types can be amended in-place and there is no need for extra libraries for each interface version).
    • Extension interfaces can be attached at runtime rather than in the type system, so there is no need to rebase downstream extensions onto newer versions of interfaces.
  • An existing AIDL interface can be used directly when its owner chooses to stabilize it. Before, an entire copy of the interface would have to be created in HIDL.

Writing an AIDL HAL interface

For an AIDL interface to be used between system and vendor, the interface needs two changes:

  • Every type definition must be annotated with @VintfStability.
  • The aidl_interface declaration needs to include stability: "vintf",.

Only the owner of an interface can make these changes.

When you make these changes, the interface must be in the VINTF manifest in order to work. Test this (and related requirements, such as verifying that released interfaces are frozen) using the VTS test vts_treble_vintf_vendor_test. You can use a @VintfStability interface without these requirements by calling either AIBinder_forceDowngradeToLocalStability in the NDK backend, android::Stability::forceDowngradeToLocalStability in the C++ backend, or android.os.Binder#forceDowngradeToSystemStability in the Java backend on a binder object before it's sent to another process. Downgrading a service to vendor stability isn't supported in Java because all apps run in a system context.

Additionally, for maximum code portability and to avoid potential problems such as unnecessary additional libraries, disable the CPP backend.

Note that the use of backends in the code example below is correct, as there are three backends (Java, NDK, and CPP). The code below tells how to select the CPP backend specifically, to disable it.

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

Finding AIDL HAL interfaces

AOSP Stable AIDL interfaces for HALs are in the same base directories as HIDL interfaces, in aidl folders.

  • hardware/interfaces
  • frameworks/hardware/interfaces
  • system/hardware/interfaces

You should put extension interfaces into other hardware/interfaces subdirectories in vendor or hardware.

Extension Interfaces

Android has a set of official AOSP interfaces with every release. When Android partners want to add functionality to these interfaces, they shouldn't change these directly because this would mean that their Android runtime is incompatible with the AOSP Android runtime. For GMS devices, avoiding changing these interfaces is also what ensures the GSI image can continue to work.

Extensions can register in two different ways:

However an extension is registered, when vendor-specific (meaning not a part of upstream AOSP) components use the interface, there is no possibility of merge conflict. However, when downstream modifications to upstream AOSP components are made, merge conflicts can result, and the following strategies are recommended:

  • the interface additions can be upstreamed to AOSP in the next release
  • interface additions which allow further flexibility, without merge conflicts, can be upstreamed in the next release

Extension Parcelables: ParcelableHolder

ParcelableHolder is a Parcelable which can contain another Parcelable. The main use case of ParcelableHolder is to make a Parcelable extensible. For example, image that device implementers expect to be able to extend an AOSP-defined Parcelable, AospDefinedParcelable, to include their value-add features.

Previously without ParcelableHolder, device implementers couldn't modify an AOSP-defined stable AIDL interface because it would be an error to add more fields:

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

As seen in the preceding code, this practice is broken because the fields added by the device implementer might have a conflict when the Parcelable is revisioned in the next releases of Android.

Using ParcelableHolder, the owner of a parcelable can define an extension point in a Parcelable.

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

Then the device implementers can define their own Parcelable for their extension.

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

Finally, the new Parcelable can be attached to the original Parcelable via the ParcelableHolder field.


// 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>();

Building against the AIDL runtime

AIDL has three different backends: Java, NDK, CPP. To use Stable AIDL, you must always use the system copy of libbinder at system/lib*/libbinder.so and talk on /dev/binder. For code on the vendor image, this means that libbinder (from the VNDK) cannot be used: this library has an unstable C++ API and unstable internals. Instead, native vendor code must use the NDK backend of AIDL, link against libbinder_ndk (which is backed by system libbinder.so), and link against the -ndk_platform libraries created by aidl_interface entries.

AIDL HAL server instance names

By convention, AIDL HAL services have an instance name of the format $package.$type/$instance. For example, an instance of the vibrator HAL is registered as android.hardware.vibrator.IVibrator/default.

Writing an AIDL HAL server

@VintfStability AIDL servers must be declared in the VINTF manifest, for example like this:

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

Otherwise, they should register an AIDL service normally. When running VTS tests, it's expected that all declared AIDL HALs are available.

Writing an AIDL client

AIDL clients must declare themselves in the compatibility matrix, for example like this:

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

Converting an existing HAL from HIDL to AIDL

Use the hidl2aidl tool to convert a HIDL interface to AIDL.

hidl2aidl features:

  • Create .aidl files based on the .hal files for the given package
  • Create build rules for the newly created AIDL package with all backends enabled
  • Create translate methods in the Java, CPP, and NDK backends for translating from the HIDL types to the AIDL types
  • Create build rules for translate libraries with required dependencies
  • Create static asserts to ensure that HIDL and AIDL enumerators have the same values in the CPP and NDK backends

Follow these steps to convert a package of .hal files to .aidl files:

  1. Build the tool located in system/tools/hidl/hidl2aidl.

    Building this tool from the latest source provides the most complete experience. You can use the latest version to convert interfaces on older branches from previous releases.

    m hidl2aidl
    
  2. Execute the tool with an output directory followed by the package to be converted.

    Optionally, use the -l argument to add the contents of a new license file to the top of all generated files. Be sure to use the correct license and date.

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

    For example:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Read through the generated files and fix any issues with the conversion.

    • conversion.log contains any unhandled issues to fix first.
    • The generated .aidl files might have warnings and suggestions that might need action. These comments begin with //.
    • Take the opportunity to clean up and make improvements to the package.
    • Check the @JavaDerive annotation for features that might be needed, such as toString or equals.
  4. Build only the targets you need.

    • Disable backends that won't be used. Prefer the NDK backend over the CPP backend, see choosing runtime.
    • Remove translate libraries or any of their generated code that won't be used.
  5. See Major AIDL/HIDL differences.

    • Using AIDL's built-in Status and exceptions typically improve the interface and remove the need for another interface-specific status type.
    • AIDL interface arguments in methods aren't @nullable by default like they were in HIDL.

Sepolicy for AIDL HALs

An AIDL service type which is visible to vendor code must have the hal_service_type attribute. Otherwise, the sepolicy configuration is the same as any other AIDL service (though there are special attributes for HALs). Here is an example definition of a HAL service context:

    type hal_foo_service, service_manager_type, hal_service_type;

For most services defined by the platform, a service context with the correct type is added already (for example, android.hardware.foo.IFoo/default would already be marked as hal_foo_service). However, if a framework client supports multiple instance names, additional instance names must be added in device-specific service_contexts files.

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

HAL attributes must be added when we create a new type of HAL. A specific HAL attribute might be associated with multiple service types (each of which may have multiple instances as we just discussed). For a HAL, foo, we have hal_attribute(foo). This macro defines attributes hal_foo_client and hal_foo_server. For a given domain, the hal_client_domain and hal_server_domain macros associate a domain with a given HAL attribute. For instance, system server being a client of this HAL corresponds to the policy hal_client_domain(system_server, hal_foo). A HAL server similarly includes hal_server_domain(my_hal_domain, hal_foo). Typically, for a given HAL attribute, we also create a domain like hal_foo_default for reference or example HALs. However, some devices use these domains for their own servers. Distinguishing between domains for multiple servers only matters if we have multiple servers which serve the same interface and need a different permission set in their implementations. In all of these macros, hal_foo is not actually an sepolicy object. Instead, this token is used by these macros to refer to the group of attributes associated with a client server pair.

However, so far, we haven't associated hal_foo_service and hal_foo (the attribute pair from hal_attribute(foo)). A HAL attribute is associated with AIDL HAL services using the hal_attribute_service macro (HIDL HALs use the hal_attribute_hwservice macro). For instance, hal_attribute_service(hal_foo, hal_foo_service). This means that hal_foo_client processes can get ahold of the HAL, and hal_foo_server processes can register the HAL. The enforcement of these registration rules is done by the context manager (servicemanager). Notice, service names might not always correspond to HAL attributes. For instance, we might see hal_attribute_service(hal_foo, hal_foo2_service). Generally though, since this implies the services are always used together, we could consider removing the hal_foo2_service and using hal_foo_service for all of our service contexts. Most HALs that set multiple hal_attribute_service are because the original HAL attribute name is not general enough and cannot be changed.

Putting this all together, an example HAL looks like this:

    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)

Attached extension interfaces

An extension can be attached to any binder interface, whether it is a top-level interface registered directly with service manager or it is a sub-interface. When getting an extension, you must confirm the type of the extension is as expected. Extensions can only be set from the process serving a binder.

Attached extensions should be used whenever an extension modifies the functionality of an existing HAL. When entirely new functionality is needed, this mechanism doesn't need to be used, and an extension interface can be registered with the service manager directly. Attached extension interfaces make the most sense when they are attached to sub-interfaces, because these hierarchies may be deep or multi-instanced. Using a global extension to mirror the binder interface hierarchy of another service would require extensive bookkeeping to provide equivalent functionality to directly attached extensions.

To set an extension on binder, use the following APIs:

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

To get an extension on a binder, use the following APIs:

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

You can find more information for these APIs in the documentation of the getExtension function in the corresponding backend. An example of how to use extensions can be found in hardware/interfaces/tests/extension/vibrator.

Major AIDL/HIDL differences

When using AIDL HALs or using AIDL HAL interfaces, be aware of the differences compared to writing HIDL HALs.

  • The AIDL language's syntax is closer to Java. HIDL syntax is similar to C++.
  • All AIDL interfaces have built-in error statuses. Instead of creating custom status types, create constant status ints in interface files and use EX_SERVICE_SPECIFIC in the CPP/NDK backends and ServiceSpecificException in the Java backend. See Error Handling.
  • AIDL does not automatically start threadpools when binder objects are sent. They must be started manually (see thread management).
  • AIDL does not abort on unchecked transport errors (HIDL Return aborts on unchecked errors).
  • AIDL can only declare one type per file.
  • AIDL arguments can be specified as in/out/inout in addition to the output parameter (there are no "synchronous callbacks").
  • AIDL uses an fd as the primitive type instead of handle.
  • HIDL uses major versions for incompatible changes and minor versions for compatible changes. In AIDL, backwards-compatible changes are done in place. AIDL has no explicit concept of major versions; instead, this is incorporated into package names. For instance, AIDL might use the package name bluetooth2.
  • AIDL doesn't inherit realtime priority by default. The setInheritRt function must be used per-binder to enable realtime priority inheritance.