Android 10 adds support for stable Android Interface Definition Language (AIDL), a new way to keep track of the application program interface (API) and application binary interface (ABI) provided by AIDL interfaces. Stable AIDL works exactly like AIDL, but the build system tracks interface compatibility, and there are restrictions on what you can do:
- Interfaces are defined in the build system with
aidl_interfaces
. - Interfaces can contain only structured data. Parcelables representing the preferred 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 example, 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.
Define 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 typeFoo
defined in a packagecom.acme
should be at<base_path>/com/acme/Foo.aidl
, where<base_path>
could be any directory related to the directory whereAndroid.bp
is. In the preceding example,<base_path>
issrcs/aidl
.local_include_dir
: The path from where the package name starts. It corresponds to<base_path>
explained above.imports
: A list ofaidl_interface
modules that this uses. If one of your AIDL interfaces uses an interface or a parcelable from anotheraidl_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 12versions
: The previous versions of the interface that are frozen underapi_dir
, Starting in Android 11, theversions
are frozen underaidl_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 withversions_with_info
for Android 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 ataidl_api/IFACE/V
. This field was introduced in Android 13, and it isn't supposed to be modified inAndroid.bp
directly. The field is added or updated by invoking*-update-api
or*-freeze-api
. Also,versions
fields is automatically migrated toversions_with_info
when a user invokes*-update-api
or*-freeze-api
.stability
: The optional flag for the stability promise of this interface. This only supports"vintf"
. Ifstability
is unset, the build system checks that the interface is backward compatible unlessunstable
is specified. Being unset corresponds to an interface with stability within this compilation context (so either all system things, for example, things insystem.img
and related partitions, or all vendor things, for example, things invendor.img
and related partitions). Ifstability
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 istrue
for thecpp
andjava
backends.host_supported
: The optional flag that when set totrue
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 totrue
, the build system neither creates the API dump for the interface nor requires it to be updated.frozen
: The optional flag that when set totrue
means that the interface has no changes since the previous version of the interface. This enables more build-time checks. When set tofalse
this means the interface is in development and has new changes so runningfoo-freeze-api
generates a new version and automatically change the value totrue
. Introduced in Android 14.backend.<type>.enabled
: These flags toggle the each of the backends that the AIDL compiler generates code for. 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 until Android 15 (AOSP experimental).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 isfalse
.backend.[cpp|ndk].additional_shared_libraries
: Introduced in Android 14, this flag adds dependencies to the native libraries. This flag is useful withndk_header
andcpp_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 whenbackend.java.platform_apis
istrue
.backend.java.platform_apis
: The optional flag that should be set totrue
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.
Write 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 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.
Use 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 your preference 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.
After 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 noowner:
field specified.AIDL_FROZEN_OWNERS="aosp test"
- build requires all stable AIDL interfaces to be frozen with theowner:
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 a previous version of the interface, and some apps 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.
Use 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 getsSTATUS_UNKNOWN_TRANSACTION
.java
backend getsandroid.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 shouldn't 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 shouldn't abort when it receives an enumerator that it doesn't know about. The server should either ignore the enumerator, 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
one-way 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 Use 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
.
Matrixes
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 to work when RELEASE_AIDL_USE_UNFROZEN
is false
.
Considering backward compatibility in implementations and device code is a new
exercise. See Use 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 sends the newest version, and new methods might return
this. This is similar to how stable AIDL clients maintain backward
compatibility with servers describes in Use 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 theaidl_interface
moduleversion
is either ofVversion-number
for the frozen versionsVlatest-frozen-version-number + 1
for the tip-of-tree (yet-to-be-frozen) version
backend
is either ofjava
for the Java backend,cpp
for the C++ backend,ndk
orndk_platform
for the NDK backend. The former is for apps, and the latter is for platform usage until Android 13. In Android 13 and later, only usendk
.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 becomesfoo-V2-backend
foo-unstable-backend
, which referred to the ToT version becomesfoo-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.
Query 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 and paste mistakes. The annotation @SuppressWarnings("static")
might
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.
Deal 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.
Convert existing AIDL to structured or 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.
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.
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).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.