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 (backwards-compatible). When this happens, their API is tracked and versioned in a file next to the AIDL interface.
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 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 example above,<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 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 in Android.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. 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. Default isfalse
.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
will generate a new version and automatically change the value totrue
. Introduced in Android 14 (AOSP experimental).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 isfalse
.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
is true.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.
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!). The primary difference in stable AIDL is how parcelables are defined. Previously, parcelables were forward declared; in stable 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: ...,
rust_libs: ["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 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 backwards 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 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 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.
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,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.
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 are 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.
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.