AIDL style guide

The best practices outlined here serve as a guide to developing AIDL interfaces effectively and with attention to flexibility of the interface, particularly when AIDL is used to define an API or interact with API surfaces.

AIDL can be used to define an API when apps need to interface with each other in a background process or need to interface with the system. For more information about developing programming interfaces in apps with AIDL, see Android Interface Definition Language (AIDL). For examples of AIDL in practice, see AIDL for HALs and Stable AIDL.

Versioning

Every backward-compatible snapshot of an AIDL API corresponds to a version. To take a snapshot, run m <module-name>-freeze-api. Whenever a client or server of the API is released (for example, in a Mainline train), you need to take a snapshot and make a new version. For system-to-vendor APIs, this should happen with the yearly platform revision.

For more details and information about the type of changes that are allowed, see Versioning interfaces.

API design guidelines

General

1. Document everything

  • Document every method for its semantics, arguments, use of built-in exceptions, service specific exceptions, and return value.
  • Document every interface for its semantics.
  • Document the semantic meaning of enums and constants.
  • Document whatever might be unclear to an implementer.
  • Provide examples where relevant.

2. Casing

Use upper camel casing for types and lower camel casing for methods, fields and arguments. For example, MyParcelable for a parcelable type and anArgument for an argument. For acronyms, consider the acronym a word (NFC -> Nfc).

[-Wconst-name] Enum values and constants should be ENUM_VALUE and CONSTANT_NAME

Interfaces

1. Naming

[-Winterface-name] An interface name should start with I like IFoo.

2. Avoid big interface with id-based "objects"

Prefer subinterfaces when there are many calls related to a specific API. This provides the following benefits:

  • Makes client or server code easier to understand
  • Makes the lifecycle of objects simpler
  • Takes advantage of binders being unforgeable.

Not recommended: A single, large interface with ID-based objects

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Recommended: Individual interfaces

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Don't mix one-way with two-way methods

[-Wmixed-oneway] Don't mix one-way with non-oneway methods, because it makes understanding the threading model complicated for clients and servers. Specifically, when reading client code of a particular interface, you need to look up for each method if that method will block or not.

4. Avoid returning status codes

Methods should avoid status codes as return values, since all AIDL methods have an implicit status return code. See ServiceSpecificException or EX_SERVICE_SPECIFIC. By convention, these values are defined as constants in an AIDL interface. More detailed information is in the Error handling section of AIDL backends.

5. Arrays as output parameters considered harmful

[-Wout-array] Methods having array output parameters, like void foo(out String[] ret) are usually bad because the output array size must be declared and allocated by the client in Java, and so the size of the array output cannot be chosen by the server. This undesirable behavior happens because of how arrays work in Java (they cannot be reallocated). Instead prefer APIs like String[] foo().

6. Avoid inout parameters

[-Winout-parameter] This can confuse clients because even in parameters look like out parameters.

7. Avoid out and inout @nullable non-array parameters

[-Wout-nullable] Since the Java backend doesn't handle @nullable annotation while other backends do, out/inout @nullable T may lead inconsistent behavior across backends. For example, non-Java backends can set an out @nullable parameter to null (in C++, setting it as std::nullopt) but the Java client can't read it as null.

Structured parcelables

1. When to use

Use structured parcelables where you have multiple data types to send.

Or, when you have a single data type but you expect that you will need to extend it in the future. For example, don't use String username. Use an extendable parcelable, like the following:

parcelable User {
    String username;
}

So that, in the future, you can extend it, as follows:

parcelable User {
    String username;
    int id;
}

2. Provide defaults explicitly

[-Wexplicit-default, -Wenum-explicit-default] Provide explicit defaults for fields.

Nonstructured parcelables

1. When to use

Nonstructured parcelables are available in Java with @JavaOnlyStableParcelable and in the NDK backend with @NdkOnlyStableParcelable. Usually, these are old and existing parcelables that can't be structured.

Constants and enums

1. Bitfields should use constant fields

Bitfields should use constant fields (for example, const int FOO = 3; in an interface).

2. Enums should be closed sets.

Enums should be closed sets. Note: only the interface owner can add enum elements. If vendors or OEMs need to extend these fields, an alternative mechanism is needed. Whenever possible, upstreaming vendor functionality should be preferred. However, in some cases, custom vendor values may be allowed through (though, vendors should have a mechanism in place to version this, perhaps AIDL itself, they shouldn't be able to conflict with each other, and these values shouldn't be exposed to 3rd party apps).

3. Avoid values like "NUM_ELEMENTS"

Since enums are versioned, values which indicate how many values are present should be avoided. In C++, this can be worked around with, enum_range<>. For Rust, use enum_values(). In Java, there's no solution yet.

Not recommended: Using numbered values

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Avoid redundant prefixes and suffixes

[-Wredundant-name] Avoid redundant or repetitive prefixes and suffixes in constants and enumerators.

Not recommended: Using a redundant prefix

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Recommended: Directly naming the enum

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] The use of FileDescriptor as an argument or the return value of an AIDL interface method is highly discouraged. Especially, when the AIDL is implemented in Java, this might cause file descriptor leak unless carefully handled. Basically, if you accept a FileDescriptor, you need to close it manually when it is no longer used.

For native backends, you are safe because FileDescriptor maps to unique_fd which is auto-closeable. But regardless of the backend language you would use, it is wise to NOT use FileDescriptor at all because this will limit your freedom to change the backend language in the future.

Instead, use ParcelFileDescriptor, which is autocloseable.

Variable units

Make sure that variable units are included in the name so that their units are well-defined and understood without needed to reference documentation

Examples

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Timestamps must indicate their reference

Timestamps (in fact, all units!) must clearly indicate their units and reference points.

Examples

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;