Binder: Android Interface Definition Language

Baiqin Wang
The Startup

--

Android interface definition language (AIDL) is used to define the contract that a Binder service object provides. With the help of AIDL developers only need to focus on implementing the business logic of the Binder service interface instead of all kinds of boilerplate code. The tool for auto-generating Java source code is aidl. It is included in Android SDK under build-tools directory. Not too long ago AIDL is only used for auto-generating Java code. But now Android includes a aidl-cpp tool to auto-generate C++ code as well. It is included in the Android Open Source Project (AOSP). If you don't want to install Android SDK or compile AOSP, you can install a standalone version of aidl and aidl-cpp with the apt tool (tested under Ubuntu 18.04 LTS):

android1001@ubuntu:~$ sudo apt install aidl

Google has a detailed documentation about AIDL [1] and another introduction about aidl-cpp [2]. So we will not go through the specifications of AIDL in this article. In this article we will go through the *.aidl files in HamKing [3] project and the auto-generated Java files to learn it in a contextual way. This sample project is already introduced a the article "Binder introduction".

Here are all the *.aidl files in the project:

There are two types of definitions: interface and parcelable. An interface defines a Binder service and aidl will auto-generate two kinds of Binder objects with it. The first one is a Stub class that extends from Binder and should be implemented by server side of the service. The other one is a Proxy class that should be used by the client side of the service. A parcelable defines a Parcelable type which is the data model Binder uses in user space. aidl will auto-generate the Parcelable class based on the definition. This is a recent feature of AIDL so you should use latest build tools version for that. Previously developers need to handcraft the Parcelable classes.

The parameters and return types in an interface method can be a raw type, a Parcelable or a Binder object. A parameter can be modified by in, out or inout and by default it is in. The in modifier means that the data in that argument only moves from client to server. Server side only receives the data in it and should not make any changes to it. The out modifier means the contrary. In this case the parameter client side provides is just a placeholder, the data will not be serialized and send to server. Instead, the placeholder will be initialized with data that server side returns. The inout is a combination of in and out. The data will be passed to server side and server will return data for that field. In short, for in data goes from client to server, for out data goes from server to client, for inout data goes both ways.

For example, the order parameter in requestOrder is inout because the client leaves the mOrderId field in order parameter empty and expecting server to fill it. For the same reason, the order parameter in pickupOrder is inout because the client is expecting the server to fill in the mBurgerImage field. All other parameters are in because the client just wants to send the data to the server.

We will copy the *.aidl files out from HamKing project to a test folder since they are the only files we care about in this article:

Then we use the aidl tool to generate Java source code for IHamKingInterface.aidl:

The is the modified version of IHamKingInterface.java file under the generated folder:

The three interface methods in the IHamKingInterface.aidl file are just added to the Java IHamKingInterface class. The Stub class is meant for server side to extend and implement and Proxy is used by client side. They both implement the IHamKingInterface but Stub does the real business logic of the interface methods. Proxy just serializes all arguments into a Parcel and calls the remote Stub to do the actual work with the help of Binder driver.

It is not difficult to figure out how aidl implements the in and out semantics. The client parameter is a in type, so it is only marshaled into _data on line 101 and then sent to server on line 111. The order parameter is a inout so it is both sent to server in _data and read back from server in _reply.

We described in the article “Binder architecture and core components” that due to client or server side bugs, the proxy side of a service may not be pointing to the correct remote Binder service. Binder framework cannot prevent this from happening but it provides an interface descriptor mechanism for both side to detect the error. You are not obligated to use this mechanism but the code that aidl generates does use it. The aidl tool auto-generates a DESCRIPTOR field to act as the identifier of the service. Line 10 calls attachInterface to attach this identifier string to the native Binder service. First of all, the proxy side can request for this identifier by using INTERFACE_TRANSACTION code. Native side will write the string in the reply Parcel and the client can detect the potential mismatch. On the other hand, server can verify the mismatch with enforceInterface method in Parcel class. enforceInterface will compare the expected descriptor string to the one sent by the client and throw an SecurityException if a mismatch is found. The client sends the expected descriptor string by writeInterfaceToken.

The code argument in transact method of an IBinder defines the transaction code of a Binder transaction. For example, requestOrder, cancelOrder and pickupOrder each has a unique transaction code. Binder driver knows nothing about the semantics of the code, it is totally a user space concept. How client and server interpret the code is based on a contract that both client and server agree. aidl is responsible of generating this contract. What it does is generating an integer code for each interface method in the .aidl file. The exact code an interface method gets depends on the order it appears in the .aidl file. With that being said, simply switching the method orders in the .aidl file makes it incompatible with the older version.

The asInterface method does what is called "interface casting". The input parameter obj can be a BinderProxy or Binder depending on whether the service is implemented in local process or in a remote process. This method will just down cast it to an interface type if it is a Binder and use a Proxy class to wrap it if it is a BinderProxy. You cannot make any assumption about whether the IBinder you receive is local or remote. For example, in the HamKing project the client app calls bindService to bind to the RemoteService. However the server app itself can bind to the RemoteService as well. In both cases you will get back an IBinder in onServiceConnected. But the actual type of the IBinder will be BinderProxy if binding from the client app and will be Binder if binding from the server app.

These are the important aspects of the auto-generated Java file. The aidl-cpp tool can do the code auto-generation in C++ as well. The version of aidl-cpp in Ubuntu 18.04 LTS distribution doesn't understand parcelable declaration, so I will only use it against ICreditCard.aidl:

If you follow the previous article “Binder architecture and core components”, the generated code should be easy to understand. The DECLARE_META_INTERFACE macro just declares the interface descriptor and interface casting facilities which is the same concept as the Java counterpart. The IMPLEMENT_META_INTERFACE macro expands to the definitions of the corresponding DECALRE_META_INTERFACE. Other than that the credit_card.cpp should be easy to understand since the code structure is the same as the Java version.

External links

[1] https://developer.android.com/guide/components/aidl

[2] https://android.googlesource.com/platform/system/tools/aidl/+/brillo-m10-dev/docs/aidl-cpp.md

[3] https://github.com/androidonekb/HamKing

--

--