4

I am a new to AIDL interface. I would like to implement AIDL interface between Java(Application layer) which is build using gradle and C++(Native layer) which is build using cmake. I need to pass a chunk of data between these two processes using AIDL interface. I am able to implement a .aidl file in application layer and able to create a service. I need to implement a aidl client in native layer and need to pass data. Can anyone suggest me how to implement AIDL which is to be build cmake.

Srinivas
  • 41
  • 1
  • 4
  • AOSP comes with a tool `aidl-cpp` for generating C++ headers and stubs from an AIDL file. You would have to add a code-generation phase to your cmake build script that calls `aidl-cpp`. In your native code, you would include the generated headers and link against the generated stubs. – f9c69e9781fa194211448473495534 Dec 14 '20 at 10:02
  • I refer the page [link](https://android.googlesource.com/platform/system/tools/aidl/+/brillo-m10-dev/docs/aidl-cpp.md) for aidl-cpp. In this page they mentioned to use android.mk file but we are using cmake to build our plugin on native layer. Is it possible to build aidl-cpp with cmake build system – Srinivas Dec 15 '20 at 06:29
  • The Android.mk files that come with AOSP have some macros for calling aidl-cpp, but it is possible to call aidl-cpp without an Android.mk. So you would have to obtain the aidl-cpp binary in some way (e.g. by checking out AOSP one time and building aidl-cpp). Once you have obtained the binary, you can place it somewhere where your CMakeLists.txt can access it and add a code generation phase to your CMakeLists.txt (see e.g. [here](https://stackoverflow.com/questions/15972898/cmake-how-to-run-a-add-custom-command-before-everything-else) for how to do code generation with cmake). – f9c69e9781fa194211448473495534 Dec 15 '20 at 09:16
  • 1
    Thank you for your suggestion.I created the service in java application using Android studio. Now I need to create a client on native layer(c++ code) which is to be linked with service created in java application and requires onBind() and serviceConnection() to connect with service as per [link](https://developer.android.com/guide/components/aidl#Expose) . Is it possible to add these api's in native layer and create connection between AIDL service and client to transfer a chunk of data. Can you please give me suggestion? – Srinivas Dec 17 '20 at 13:45
  • @Srinivas, I need little more clarity on your question. You have an Android bound service (Java) that implement an AIDL interface. Your Java client component (like an Activity) needs to bind to the service and get an `IBinder` through a `ServiceConnection`. Then you need to pass that Java Binder to a native C++ layer via JNI and talk to the service from the C++ layer. If this is what you need to do, it can be done with Android's new [NdkBinder](https://developer.android.com/ndk/reference/group/ndk-binder) API (available since API level 29). – Lakindu Jan 15 '21 at 21:19
  • @Lakindu, I have an application which is running on different process and it is implemented in java and also I have one native service (like plugin) which is implemented in java. The API calls from application to native will be happen through JNI which is implemented by Android. But my requirement is, I need to pass a chunk of data from application to native code using AIDL(to aviod JNI only to pass chunk of data). In tried by using aidl-cpp. But I'm not succeeded. Can you please suggest me how can I do that? – Srinivas Feb 19 '21 at 05:11
  • @Srinivas, Check if this is what you need : https://github.com/lakinduboteju/AndroidNdkBinderExamples – Lakindu Feb 20 '21 at 08:31

2 Answers2

6

To obtain the aidl-cpp binary, you would have to set up the AOSP source code using the instructions here and the first few instructions here. Once you have set up the build environment, you can build the binary with make aidl-cpp. The binary can then be found e.g. in out/host/linux-x86/bin/aidl-cpp. You only have to do this once, after you have obtained the binary you no longer need the AOSP code (though it is nice to have the code around to quickly search for examples).

Regarding the CMake part, as discussed in the comment, once you have build the aidl-cpp binary, you can use the CMake command add_custom_target for code generation.

The aidl-cpp command provides the following instructions:

usage: aidl-cpp INPUT_FILE HEADER_DIR OUTPUT_FILE

OPTIONS:
   -I<DIR>   search path for import statements
   -d<FILE>  generate dependency file
   -ninja    generate dependency file in a format ninja understands

INPUT_FILE:
   an aidl interface file
HEADER_DIR:
   empty directory to put generated headers
OUTPUT_FILE:
   path to write generated .cpp code

So when you call the command from cmake, you would have to provide your AIDL, then a directory in which the generated header files should be stored, and the name under which the generated cpp file should be stored. E.g. aidl-cpp ISomeInterface.aidl ./generated-headers ./generated-cpp/ISomeInterface.cpp. If you want to use a custom data type (see the last section of this answer), you would also have to pass the -I flag to point to the declaration of your custom data types.

Creating a binder client in C++

Once you have generated the header files and the cpp file with aidl-cpp, you can then connect to your service from C++. Here is an example from the AOSP source code:

  • There is an AIDL file called IDropboxManagerService.

  • The interface defined by the AIDL is implemented as DropboxManagerService in Java.

  • The service is called from C++ here:

    • Note the include of <com/android/internal/os/IDropBoxManagerService.h at the beginning of the file (link). This is the header file that has been generated by aidl-cpp from the AIDL file.

    • The default service manager is used to look-up the service called "dropbox":

      defaultServiceManager()->getService(android::String16("dropbox"))
      
    • The result from getService is cast to the interface defined in the generated header file:

      sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(...)
      
    • The service object now holds a proxy object on which you can call the methods defined in the AIDL. The proxy object will then forward these calls via Binder to the Java service (this is implemented in the cpp file generated by aidl-cpp).

      For example, the AIDL defines a method void add(in DropBoxManager.Entry entry);. The C++ code calls this method with Status status = service->add(entry);. You can check the result of the transaction with status.isOk().

Sending custom data types

Regarding data types, you can find a table with the mapping of C++ types to Java types here.

If you want to send custom objects, you would have to define a C++ class and a Java class, which both implement the Parcelable interface. This interface requires to implement two methods writeToParcel / readFromParcel in C++ and writeToParcel / createFromParcel in Java. You would have to provide matching implementations on each side, so that calling e.g. writeToParcel on the C++ object produces a flat binary encoding that can be read by readFromParcel on the Java side.

You would also have to provide an AIDL file that points to the header for your custom object using the cpp_header directive. See the following example:

  • In the DropBoxManagerService example from the previous section, we saw an object of type Entry.

    • On the C++ side, the class is declared here and implemented here.

    • On the Java side, the class is defined here.

  • In AIDL, a parcelable is declared here. Note this does not define any implementation details, it just declares there is a class of this name. It also points to the C++ header, which will be included by aidl-cpp.

  • The AIDL for the DropBoxManagerService that we looked at in the previous example imports the AIDL for the parcelable here. This allows to use this data type in the AIDL method declarations.

Based on these steps, the Entry class defined in C++ can then be send from C++ to Java. When passing the Entry object to the proxy object in C++, the cpp that was generated by aidl-cpp will use the writeToParcel function implemented for the Entry class to serialize the class into a flat binary encoding. On the Java side, the encoding is received and an Entry Java object is constructed from the encoding.

  • If I use the command `aidl-cpp INPUT_FILE HEADER_DIR OUTPUT_FILE` to build .aidl file, then is cmake really required? – Srinivas Dec 18 '20 at 13:17
  • You could also run this manually one time, then you would not have to call aidl-cpp from cmake. The only downside is that you would have to remember to rerun the command if changes are made to the AIDL file. Whereas if you call the command from your cmake, it should be rerun automatically. So if the AIDL file is changed rarely, then you don't really need to adapt the cmake file, if it is changed often then adapting the cmake file would make it more convenient. – f9c69e9781fa194211448473495534 Dec 18 '20 at 14:05
3

This example consist of an Activity, who binds a Service (Java) and passes the IBinder object to C++ JNI layer. Communication with the Service happens in JNI layer using NDK Binder APIs.

AIDL.

src/main/aidl/com/example/IMyService.aidl

package com.example;

import com.example.ComplexType;

interface IMyService
{
    ComplexType returnComplexType(int anInt,
                    long aLong, boolean aBoolean,
                    float aFloat, double aDouble,
                    String aString);
}

src/main/aidl/com/example/ComplexType.aidl

package com.example;

parcelable ComplexType cpp_header "ComplexType.h";

build.gradle file has a Gradle task (compileAidlNdk) to auto-generate NDK C++ binder source files for IMyService.aidl.

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion '29.0.3'
    
    defaultConfig {
        minSdkVersion 29
        
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++17"
            }
        }
    }
    
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
    
    ...
}

task compileAidlNdk() {
    doLast {
        def aidlCpp = [android.sdkDirectory,
                       'build-tools',
                       android.buildToolsVersion,
                       'aidl'].join(File.separator)

        def outDir = [projectDir.absolutePath,
                     'src', 'main', 'cpp', 'aidl'].join(File.separator)

        def headerOutDir = [projectDir.absolutePath,
                           'src', 'main', 'cpp', 'includes'].join(File.separator)

        def searchPathForImports = [projectDir.absolutePath,
                                   'src', 'main', 'aidl'].join(File.separator)

        def aidlFile = [projectDir.absolutePath,
                       'src', 'main', 'aidl',
                       'com', 'example', 'IMyService.aidl'].join(File.separator)

        exec {
            executable(aidlCpp)
            args('--lang=ndk',
                 '-o', outDir,
                 '-h', headerOutDir,
                 '-I', searchPathForImports,
                 aidlFile)
        }
    }
}

afterEvaluate {
    preBuild.dependsOn(compileAidlNdk)
}

src/main/java/com/example/ndkbinderclient/MainActivity.java

package com.example.ndkbinderclient;

public class MainActivity extends AppCompatActivity implements ServiceConnection
{
    static
    {
        System.loadLibrary("native-lib");
    }
    
    private volatile boolean mIsServiceConnected = false;
    private final ConditionVariable mServiceConnectionWaitLock = new ConditionVariable();
    
    public native void onServiceConnected(IBinder binder);
    public native void onServiceDisconnected();
    public native void talkToService();
    
    @Override
    protected void onResume()
    {
        super.onResume();

        Intent intent = new Intent();
        intent.setClassName("com.example",
                "com.example.javabinderservice.MyService");

        bindService(intent, this, BIND_AUTO_CREATE);

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                // Not connected to service yet?
                while(!mIsServiceConnected)
                {
                    // waits for service connection
                    mServiceConnectionWaitLock.block();
                }

                talkToService();
            }
        }).start();
    }
    
    @Override
    protected void onPause()
    {
        unbindService(this);

        mIsServiceConnected = false;

        onServiceDisconnected();

        super.onPause();
    }
    
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder)
    {
        onServiceConnected(iBinder);

        mIsServiceConnected = true;

        // breaks service connection waits
        mServiceConnectionWaitLock.open();
    }
    
    @Override
    public void onServiceDisconnected(ComponentName componentName)
    {
        mIsServiceConnected = false;

        onServiceDisconnected();
    }
}

src/main/cpp/native-lib.cpp

#include <jni.h>
#include <aidl/com/example/IMyService.h>
#include <android/binder_ibinder_jni.h>

std::shared_ptr<IMyService> g_spMyService;

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_onServiceConnected(
        JNIEnv* env,
        jobject /* this */,
        jobject binder)
{
    AIBinder* pBinder = AIBinder_fromJavaBinder(env, binder);

    const ::ndk::SpAIBinder spBinder(pBinder);
    g_spMyService = IMyService::fromBinder(spBinder);
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_onServiceDisconnected(
        JNIEnv* env,
        jobject /* this */)
{
    g_spMyService = nullptr;
}

extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkbinderclient_MainActivity_talkToService(
        JNIEnv* env,
        jobject /* this */)
{
    ComplexType returnedComplexObject;

    ScopedAStatus returnComplexTypeResult = g_spMyService->returnComplexType(2021,
            65535000, true, 3.14f, 3.141592653589793238,
            "Hello, World!", &returnedComplexObject);
}

src/main/cpp/includes/ComplexType.h

#pragma once

#include <android/binder_status.h>

class ComplexType
{
public:
    int i_Int;
    long l_Long;
    bool b_Boolean;
    float f_Float;
    double d_Double;
    std::string s_String;
    
public:
    binder_status_t readFromParcel(const AParcel* pParcel)
    {
        int32_t iNotNull;
        AParcel_readInt32(pParcel, &iNotNull);

        AParcel_readInt32(pParcel, &i_Int);

        AParcel_readInt64(pParcel, &l_Long);

        AParcel_readBool(pParcel, &b_Boolean);

        AParcel_readFloat(pParcel, &f_Float);

        AParcel_readDouble(pParcel, &d_Double);

        ndk::AParcel_readString(pParcel, &s_String);

        return STATUS_OK;
    }
    
    binder_status_t writeToParcel(AParcel* pParcel) const
    {
        int32_t iNotNull = 1;
        AParcel_writeInt32(pParcel, iNotNull);

        AParcel_writeInt32(pParcel, i_Int);

        AParcel_writeInt64(pParcel, l_Long);

        AParcel_writeBool(pParcel, b_Boolean);

        AParcel_writeFloat(pParcel, f_Float);

        AParcel_writeDouble(pParcel, d_Double);

        ndk::AParcel_writeString(pParcel, s_String);

        return STATUS_OK;
    }
};

src/main/cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)

add_library (
        native-lib
        SHARED
        native-lib.cpp
        aidl/com/example/IMyService.cpp
)

target_include_directories (
        native-lib
        PRIVATE
        includes
)

target_link_libraries (
        native-lib
        binder_ndk
)

src/main/java/com/example/ndkbinderservice/MyService.java

package com.example.javabinderservice;

public class MyService extends Service
{
    private IBinder mBinder;
    
    @Override
    public void onCreate()
    {
        super.onCreate();

        mBinder = new MyServiceBinder();
    }
    
    @Override
    public IBinder onBind(Intent intent)
    {
        return mBinder;
    }
    
    private static class MyServiceBinder extends IMyService.Stub
    {
        @Override
        public ComplexType returnComplexType(int anInt,
                               long aLong, boolean aBoolean,
                               float aFloat, double aDouble,
                               String aString) throws RemoteException
        {
            return new ComplexType(anInt, aLong, aBoolean, aFloat,
                                   aDouble, aString);
        }
    }
}

src/main/java/com/example/ComplexType.java

public class ComplexType implements Parcelable
{
    public final int mInt;
    public final long mLong;
    public final boolean mBoolean;
    public final float mFloat;
    public final double mDouble;
    public final String mString;
    
    protected ComplexType(Parcel in)
    {
        mInt = in.readInt();
        mLong = in.readLong();
        mBoolean = in.readBoolean();
        mFloat = in.readFloat();
        mDouble = in.readDouble();
        mString = in.readString();
    }
    
    @Override
    public void writeToParcel(Parcel parcel, int i)
    {
        parcel.writeInt(mInt);
        parcel.writeLong(mLong);
        parcel.writeBoolean(mBoolean);
        parcel.writeFloat(mFloat);
        parcel.writeDouble(mDouble);
        parcel.writeString(mString);
    }
    
    ...
}

src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">
    
    <application>
    
        <activity android:name=".ndkbinderclient.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <service
            android:name=".ndkbinderservice.MyService"
            android:exported="true">
        </service>
    
    </application>

</manifest>

Complete source code can be found at https://github.com/lakinduboteju/AndroidNdkBinderExamples

Lakindu
  • 1,010
  • 8
  • 14