Stable AIDL

Android 10 adds support for stable Android Interface Definition Language (AIDL), a new way to keep track of the application program interface (API)/application binary interface (ABI) provided by AIDL interfaces. Stable AIDL has the following key differences from AIDL:

  • Interfaces are defined in the build system with aidl_interfaces.
  • Interfaces can contain only structured data. Parcelables representing the desired types are automatically created based on their AIDL definition and are automatically marshalled and unmarshalled.
  • Interfaces can be declared as stable (backward compatible). When this happens, their API is tracked and versioned in a file next to the AIDL interface.

Structured versus stable AIDL

Structured AIDL refers to types defined purely in AIDL. For instance, a parcelable declaration (a custom parcelable) isn't structured AIDL. Parcelables with their fields defined in AIDL are called structured parcelables.

Stable AIDL requires structured AIDL so that the build system and compiler can understand if changes made to parcelables are backward compatible. However, not all structured interfaces are stable. To be stable, an interface must use only structured types, and it also must use the following versioning features. Conversely, an interface isn't stable if the core build system is used to build it or if unstable:true is set.

Defining an AIDL interface

A definition of aidl_interface looks like this:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name: The name of the AIDL interface module that uniquely identifies an AIDL interface.
  • srcs: The list of AIDL source files that compose the interface. The path for an AIDL type Foo defined in a package com.acme should be at <base_path>/com/acme/Foo.aidl, where <base_path> could be any directory related to the directory where Android.bp is. In the example above, <base_path> is srcs/aidl.
  • local_include_dir: The path from where the package name starts. It corresponds to <base_path> explained above.
  • imports: A list of aidl_interface modules that this uses. If one of your AIDL interfaces uses an interface or a parcelable from another aidl_interface, put its name here. This can be the name by itself, to refer to the latest version, or the name with the version suffix (such as -V1) to refer to a specific version. Specifying a version has been supported since Android 12
  • versions: The previous versions of the interface that are frozen under api_dir, Starting in Android 11, the versions are frozen under aidl_api/name. If there are no frozen versions of an interface, this shouldn't be specified, and there won't be compatibility checks. This field has been replaced with versions_with_info for 13 and higher.
  • versions_with_info: List of tuples, each of which contains the name of a frozen version and a list with version imports of other aidl_interface modules that this version of the aidl_interface imported. The definition of the version V of an AIDL interface IFACE is located at aidl_api/IFACE/V. This field was introduced in Android 13, and it isn't supposed to be modified in Android.bp directly. The field is added or updated by invoking *-update-api or *-freeze-api. Also, versions fields is automatically migrated to versions_with_info when a user invokes *-update-api or *-freeze-api.
  • stability: The optional flag for the stability promise of this interface. Currently only supports "vintf". If this is unset, this corresponds to an interface with stability within this compilation context (so an interface loaded here can only be used with things compiled together, for example on system.img). If this is set to "vintf", this corresponds to a stability promise: the interface must be kept stable as long as it is used.
  • gen_trace: The optional flag to turn the tracing on or off. Starting in Android 14 the default is true for the cpp and java backends.
  • host_supported: The optional flag that when set to true makes the generated libraries available to the host environment.
  • unstable: The optional flag used to mark that this interface doesn't need to be stable. When this is set to true, the build system neither creates the API dump for the interface nor requires it to be updated.
  • frozen: The optional flag that when set to true means that the interface has no changes since the previous version of the interface. This enables more build-time checks. When set to false this means the interface is in development and has new changes so running foo-freeze-api will generate a new version and automatically change the value to true. Introduced in Android 14.
  • backend.<type>.enabled: These flags toggle the each of the backends that the AIDL compiler generates code for. Currently, four backends are supported: Java, C++, NDK, and Rust. Java, C++, and NDK backends are enabled by default. If any of these three backends isn't needed, it needs to be disabled explicitly. Rust is disabled by default.
  • backend.<type>.apex_available: The list of APEX names that the generated stub library is available for.
  • backend.[cpp|java].gen_log: The optional flag that controls whether to generate additional code for gathering information about the transaction.
  • backend.[cpp|java].vndk.enabled: The optional flag to make this interface a part of VNDK. Default is false.
  • backend.[cpp|ndk].additional_shared_libraries: Introduced in Android 14, this flag adds dependencies to the native libraries. This flag is useful with ndk_header and cpp_header.
  • backend.java.sdk_version: The optional flag for specifying the version of the SDK that the Java stub library is built against. The default is "system_current". This shouldn't be set when backend.java.platform_apis is true.
  • backend.java.platform_apis: The optional flag that should be set to true when the generated libraries need to build against the platform API rather than the SDK.

For each combination of the versions and the enabled backends, a stub library is created. For how to refer to the specific version of the stub library for a specific backend, see Module naming rules.

Writing AIDL files

Interfaces in stable AIDL are similar to traditional interfaces, with the exception that they aren't allowed to use unstructured parcelables (because these aren't stable! see Structured versus stable AIDL). The primary difference in stable AIDL is how parcelables are defined. Previously, parcelables were forward declared; in stable (and therefore structured) AIDL, parcelables fields and variables are defined explicitly.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

A default is currently supported (but not required) for boolean, char, float, double, byte, int, long, and String. In Android 12, defaults for user-defined enumerations are also supported. When a default is not specified, a 0-like or empty value is used. Enumerations without a default value are initialized to 0 even if there is no zero enumerator.

Using stub libraries

After adding stub libraries as a dependency to your module, you can include them into your files. Here are examples of stub libraries in the build system (Android.mk can also be used for legacy module definitions):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Example in C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Example in Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Example in Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Versioning interfaces

Declaring a module with name foo also creates a target in the build system that you can use to manage the API of the module. When built, foo-freeze-api adds a new API definition under api_dir or aidl_api/name, depending on the Android version, and adds a .hash file, both representing the newly frozen version of the interface. foo-freeze-api also updates the versions_with_info property to reflect the additional version and imports for the version. Basically, imports in versions_with_info is copied from imports field. But the latest stable version is specified in imports in versions_with_info for the import which doesn't have an explicit version. Once the versions_with_info property is specified, the build system runs compatibility checks between frozen versions and also between Top of Tree (ToT) and the latest frozen version.

In addition, you need to manage ToT version's API definition. Whenever an API is updated, run foo-update-api to update aidl_api/name/current which contains ToT version's API definition.

To maintain the stability of an interface, owners can add new:

  • Methods to the end of an interface (or methods with explicitly defined new serials)
  • Elements to the end of a parcelable (requires a default to be added for each element)
  • Constant values
  • In Android 11, enumerators
  • In Android 12, fields to the end of a union

No other actions are permitted, and no one else can modify an interface (otherwise they risk collision with changes that an owner makes).

To test that all interfaces are frozen for release, you can build with the following environmental variables set:

  • AIDL_FROZEN_REL=true m ... - build requires all stable AIDL interfaces to be frozen which have no owner: field specified.
  • AIDL_FROZEN_OWNERS="aosp test" - build requires all stable AIDL interfaces to be frozen with the owner: field specified as "aosp" or "test".

Stability of imports

Updating the versions of imports for frozen versions of an interface is backward compatible at the Stable AIDL layer. However, updating these requires updating all servers and clients which use the old version of the interface, and some applications may be confused when mixing different versions of types. Generally, for types-only or common packages, this is safe because code needs to already be written to handle unknown types from IPC transactions.

In Android platform code android.hardware.graphics.common is the biggest example of this type of version upgrade.

Using versioned interfaces

Interface methods

At runtime, when trying to call new methods on an old server, new clients get either an error or an exception, depending on the backend.

  • cpp backend gets ::android::UNKNOWN_TRANSACTION.
  • ndk backend gets STATUS_UNKNOWN_TRANSACTION.
  • java backend gets android.os.RemoteException with a message saying the API is not implemented.

For strategies to handle this see querying versions and using defaults.

Parcelables

When new fields are added to parcelables, old clients and servers drop them. When new clients and servers receive old parcelables, the default values for new fields are automatically filled in. This means that defaults need to be specified for all new fields in a parcelable.

Clients should not expect servers to use the new fields unless they know the server is implementing the version that has the field defined (see querying versions).

Enums and constants

Similarly, clients and servers should either reject or ignore unrecognized constant values and enumerators as appropriate, since more may be added in the future. For example, a server should not abort when it receives an enumerator that it doesn't know about. It should either ignore it, or return something so the client knows it's unsupported in this implementation.

Unions

Trying to send a union with a new field fails if the receiver is old and doesn't know about the field. The implementation will never see the union with the new field. The failure is ignored if it's a oneway transaction; otherwise the error is BAD_VALUE(for the C++ or NDK backend) or IllegalArgumentException(for the Java backend). The error is received if the client is sending a union set to the new field to an old server, or when it's an old client receiving the union from a new server.

Flag-based development

In-development (unfrozen) interfaces can't be used on release devices, because they aren't guaranteed to be backward compatible.

AIDL supports run time fallback for these unfrozen interface libraries in order for code to be written against the latest unfrozen version and still be used on release devices. The backward-compatible behavior of clients is similar to existing behavior and with the fallback, the implementations also need to follow those behaviors. See Using versioned interfaces.

AIDL build flag

The flag that controls this behavior is RELEASE_AIDL_USE_UNFROZEN defined in build/release/build_flags.bzl. true means the unfrozen version of the interface is used at run time and false means the libraries of the unfrozen versions all behave like their last frozen version. You can override the flag to true for local development, but must revert it to false before release. Typically development is done with a configuration that has the flag set to true.

Compatibility matrix and manifests

Vendor interface objects (VINTF objects) define what versions are expected, and what versions are provided on either side of the vendor interface.

Most non-Cuttlefish devices target the latest compatibility matrix only after interfaces are frozen, so there is no difference in the AIDL libraries based on RELEASE_AIDL_USE_UNFROZEN.

Matrices

Partner-owned interfaces are added to device-specific or product-specific compatibility matrixes that the device targets during development. So when a new, unfrozen version of an interface is added to a compatibility matrix, the previous frozen versions needs to remain for RELEASE_AIDL_USE_UNFROZEN=false. You can handle this by using different compatibility matrix files for different RELEASE_AIDL_USE_UNFROZEN configurations or allowing both versions in a single compatibility matrix file that is used in all configurations.

For example, when adding an unfrozen version 4, use <version>3-4</version>.

When version 4 is frozen you can remove version 3 from the compatibility matrix because the frozen version 4 is used when RELEASE_AIDL_USE_UNFROZEN is false.

Manifests

In Android 15 (AOSP experimental), a change in libvintf is introduced to modify the manifest files at build time based on the value of RELEASE_AIDL_USE_UNFROZEN.

The manifests and the manifest fragments declare which version of an interface a service implements. When using the latest unfrozen version of an interface, the manifest must be updated to reflect this new version. When RELEASE_AIDL_USE_UNFROZEN=false the manifest entries are adjusted by libvintf to reflect the change in the generated AIDL library. The version is modified from the unfrozen version, N, to the last frozen version N - 1. Therefore, users don't need to manage multiple manifests or manifest fragments for each of their services.

HAL client changes

HAL client code must be backward compatible with each previous supported frozen version. When RELEASE_AIDL_USE_UNFROZEN is false the services always look like the last frozen version or earlier (for example, calling new unfrozen methods returns UNKNOWN_TRANSACTION, or new parcelable fields have their default values). Android framework clients are required to be backward compatible with additional previous versions, but this is a new detail for vendor clients and clients of partner-owned interfaces.

HAL implementation changes

The largest difference in HAL development with flag-based development is the requirement for HAL implementations to be backward compatible with the last frozen version in order to work when RELEASE_AIDL_USE_UNFROZEN is false. Considering backward compatibility in implementations and device code is a new exercise. See Using versioned interfaces.

The backward compatibility considerations are generally the same for the clients and servers, and for framework code and vendor code, but there are subtle differences that you need to be aware of, as you're now effectively implementing two versions that use the same source code (the current, unfrozen version).

Example: An interface has three frozen versions. The interface is updated with a new method. The client and service are both updated to use the new version 4 library. Because the V4 library is based on an unfrozen version of the interface, it behaves like the last frozen version, version 3, when RELEASE_AIDL_USE_UNFROZEN is false, and prevents the use of the new method.

When the interface is frozen, all values of RELEASE_AIDL_USE_UNFROZEN use that frozen version, and the code handling the backward compatibility can be removed.

When calling methods on callbacks, you must gracefully handle the case when UNKNOWN_TRANSACTION is returned. Clients might be implementing two different versions of a callback based on the release configuration, so you can't assume the client will send the newest version, and new methods might return this. This is similar to how stable AIDL clients maintain backward compatibility with servers describes in Using versioned interfaces.

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

New fields in existing types (parcelable, enum, union) might not exist or contain their default values when RELEASE_AIDL_USE_UNFROZEN is false and the values of new fields a service tries to send are dropped on the way out of the process.

New types added in this unfrozen version can't be sent or received through the interface.

The implementation never gets a call for new methods from any clients when RELEASE_AIDL_USE_UNFROZEN is false.

Be careful to use new enumerators only with the version they're introduced in, and not the previous version.

Normally, you use foo->getInterfaceVersion() to see which version the remote interface is using. However with flag-based versioning support, you're implementing two different versions, so you might want to get the version of the current interface. You can do this by getting the interface version of the current object, for example, this->getInterfaceVersion() or the other methods for my_ver. See Querying the interface version of the remote object for more information.

New VINTF stable interfaces

When a new AIDL interface package is added there is no last frozen version, so there is no behavior to fall back to when RELEASE_AIDL_USE_UNFROZEN is false. Don't use these interfaces. When RELEASE_AIDL_USE_UNFROZEN is false, Service Manager won't allow the service to register the interface and the clients won't find it.

You can add the services conditionally based on the value of the RELEASE_AIDL_USE_UNFROZEN flag in the device makefile:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

If the service is a part of a larger process so you can't add it to the device conditionally, you can check to see if the service is declared with IServiceManager::isDeclared(). If it's declared and failed to register, then abort the process. If it isn't declared, then it's expected to fail to register.

Cuttlefish as a development tool

Every year after the VINTF is frozen we adjust the framework compatibility matrix (FCM) target-level and the PRODUCT_SHIPPING_API_LEVEL of Cuttlefish so they reflect devices launching with next year’s release. We adjust target-level and PRODUCT_SHIPPING_API_LEVEL to make sure there is some launching device that is tested and meets the new requirements for next year’s release.

When RELEASE_AIDL_USE_UNFROZEN is true, Cuttlefish is used for development of future Android releases. It targets next year's Android release's FCM level and PRODUCT_SHIPPING_API_LEVEL, requiring it to satisfy the next release's Vendor Software Requirements (VSR).

When RELEASE_AIDL_USE_UNFROZEN is false, Cuttlefish has the previous target-level and PRODUCT_SHIPPING_API_LEVEL to reflect a release device. In Android 14 and lower, this differentiation would be accomplished with different Git branches that don't pick up the change to FCM target-level, shipping API level, or any other code targeting the next release.

Module naming rules

In Android 11, for each combination of the versions and the backends enabled, a stub library module is automatically created. To refer to a specific stub library module for linking, don't use the name of the aidl_interface module, but the name of the stub library module, which is ifacename-version-backend, where

  • ifacename: name of the aidl_interface module
  • version is either of
    • Vversion-number for the frozen versions
    • Vlatest-frozen-version-number + 1 for the tip-of-tree (yet-to-be-frozen) version
  • backend is either of
    • java for the Java backend,
    • cpp for the C++ backend,
    • ndk or ndk_platform for the NDK backend. The former is for apps, and the latter is for platform usage,
    • rust for Rust backend.

Assume that there is a module with name foo and its latest version is 2, and it supports both NDK and C++. In this case, AIDL generates these modules:

  • Based on version 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Based on version 2 (the latest stable version)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Based on ToT version
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Compared to Android 11,

  • foo-backend, which referred to the latest stable version becomes foo-V2-backend
  • foo-unstable-backend, which referred to the ToT version becomes foo-V3-backend

The output file names are always the same as module names.

  • Based on version 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Based on version 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Based on ToT version: foo-V3-(cpp|ndk|ndk_platform|rust).so

Note that the AIDL compiler doesn't create either an unstable version module, or a non-versioned module for a stable AIDL interface. As of Android 12, the module name generated from a stable AIDL interface always includes its version.

New meta interface methods

Android 10 adds several meta interface methods for the stable AIDL.

Querying the interface version of the remote object

Clients can query the version and hash of the interface that the remote object is implementing and compare the returned values with the values of the interface that the client is using.

Example with the cpp backend:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Example with the ndk (and the ndk_platform) backend:

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Example with the java backend:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

For Java language, the remote side MUST implement getInterfaceVersion() and getInterfaceHash() as follows (super is used instead of IFoo to avoid copy/paste mistakes. The annotation @SuppressWarnings("static") may be needed to disable warnings, depending on the javac configuration):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

This is because the generated classes (IFoo, IFoo.Stub, etc.) are shared between the client and server (for example, the classes can be in the boot classpath). When classes are shared, the server is also linked against the newest version of the classes even though it might have been built with an older version of the interface. If this meta interface is implemented in the shared class, it always returns the newest version. However, by implementing the method as above, the version number of the interface is embedded in the server's code (because IFoo.VERSION is a static final int that is inlined when referenced) and thus the method can return the exact version the server was built with.

Dealing with older interfaces

It's possible that a client is updated with the newer version of an AIDL interface but the server is using the old AIDL interface. In such cases, calling a method on an old interface returns UNKNOWN_TRANSACTION.

With stable AIDL, clients have more control. In the client side, you can set a default implementation to an AIDL interface. A method in the default implementation is invoked only when the method isn't implemented in the remote side (because it was built with an older version of the interface). Since defaults are set globally, they should not be used from potentially shared contexts.

Example in C++ in Android 13 and later:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Example in Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

You don't need to provide the default implementation of all methods in an AIDL interface. Methods that are guaranteed to be implemented in the remote side (because you're certain that the remote is built when the methods were in the AIDL interface description) don't need to be overridden in the default impl class.

Converting existing AIDL to structured/stable AIDL

If you have an existing AIDL interface and code that uses it, use the following steps to convert the interface to a stable AIDL interface.

  1. Identify all of the dependencies of your interface. For every package the interface depends on, determine if the package is defined in stable AIDL. If not defined, the package must be converted.

  2. Convert all parcelables in your interface into stable parcelables (the interface files themselves can remain unchanged). Do this by expressing their structure directly in AIDL files. Management classes must be rewritten to use these new types. This can be done before you create an aidl_interface package (below).

  3. Create an aidl_interface package (as described above) that contains the name of your module, its dependencies, and any other information you need. To make it stabilized (not just structured), it also needs to be versioned. For more information, see Versioning interfaces.