Google is committed to advancing racial equity for Black communities. See how.

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 (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: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name: The name of the AIDL interface module that uniquely identies 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: The optional list of other AIDL interface modules that this interface want to import. The AIDL types defined in the imported AIDL interfaces are accessible with the import statement.
  • versions: The previous versions of the interface that are frozen under api_dir, Starting in Android R, 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.
  • 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 is false.
  • 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.
  • backend.<type>.enabled: These flags toggle the each of the backends that the AIDL compiler will generate code for. Currently, three backends are supported: java, cpp, and ndk. The backends are all enabled by default. When a specific backend is not needed, it needs to be disabled explicitly.
  • 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.java.platform_apis: The optional flag that controls whether the Java stub library is built against the private APIs from the platform. This should be set to "true" when stability is set to "vintf".
  • 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. See Module naming rules for how to refer to the specific version of the stub library for a specific backend.

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.

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"],
    ...
}

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

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, for the next version of the interface. Building this also updates the versions property to reflect the additional version. Once the versions property is specified, the build system runs compatibility checks between frozen versions and also between Top of Trunk (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 parcel (requires a default to be added for each element)
  • Constant values
  • In Android R, enumerators

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

At runtime, when trying to call new methods on an old server, new clients automatically get UNKNOWN_TRANSACTION. For strategies to handle this see querying verions and using defaults. When new fields are added to parcelables, old clients/servers drop them. When new clients/servers receive old parcelables, the default values for new fields are automatically filled in. This means that defaults should be specified for all new fields in a parcelable. Similarly, clients/servers should either reject or ignore unrecognized constant values and enumerators as appropriate, since more may be added in the future.

Module naming rules

In R, 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
    • unstable 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.

For the latest frozen version, omit the version field except Java target. In other words, module-Vlatest-frozen-version-(cpp|ndk|ndk_platform) is not generated.

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)
  • Based on version 2 (the latest stable version)
    • foo-(java|cpp|ndk|ndk_platform)
    • foo-V2-java (the content is identical as foo-java)
  • Based on ToT version
    • foo-unstable-(java|cpp|ndk|ndk_platform)

In most cases, the output file names are the same as module names. However, for the ToT version of a C++ or NDK module, the output file name is different from the module name.

For example, the output file name of foo-unstable-cpp is foo-V3-cpp.so, not foo-unstable-cpp.so as shown below.

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

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 of the interface that the remote object is implementing and compare the returned version with the interface version 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
}

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
}

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
}

For Java language, the remote side MUST implement getInterfaceVersion() as follows:

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

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++:

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

// once per an interface in a process
IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));

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.

  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.