Android Emulator Development, Anbox, & Waydroid

Implementing and Testing Custom System Services in AOSP Emulator ROMs: A Deep Dive

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Custom AOSP System Services

The Android Open Source Project (AOSP) provides a powerful, modular framework for device manufacturers and advanced developers to customize and extend the Android operating system. One of the most common and impactful ways to introduce new functionality deeply integrated with the OS is by implementing custom system services. These services run within the privileged environment of the system_server process or as standalone system processes, offering capabilities unavailable to regular applications. This guide will walk you through the entire process of defining, implementing, building, and testing a custom system service within an AOSP emulator ROM.

While the focus here is on emulator ROMs, the principles apply equally to physical hardware, making this a foundational skill for anyone looking to modify or extend Android at a system level.

Prerequisites and Environment Setup

Before diving into service implementation, ensure you have a robust AOSP build environment set up. This typically involves:

  • A Linux-based workstation (Ubuntu recommended).
  • At least 200GB of free disk space and 16GB of RAM.
  • Familiarity with Git and basic shell commands.
  • A synced AOSP source tree (e.g., Android 12 or 13).

If you haven’t already, sync the AOSP source code:

mkdir aosp && cd aosp repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_rXX # Replace rXX with a recent tag repo sync -j$(nproc)

Set up the build environment:

source build/envsetup.sh

Defining the Custom Service Interface (AIDL)

Android Interface Definition Language (AIDL) is crucial for defining the programmatic interface that clients will use to interact with your custom service across process boundaries. It allows you to define methods with parameters and return values that can be passed between different processes.

Create the AIDL File

Let’s create a simple service that can add two integers. We’ll define its AIDL in a new file, for example, frameworks/base/core/java/android/os/IMyCustomService.aidl:

// IMyCustomService.aidl package android.os; interface IMyCustomService {    /**     * Adds two integers and returns the result.     */    int add(int a, int b);}

The package declaration is vital as it dictates where the generated Java interface will reside.

Build AIDL

When you build AOSP, the build system will automatically generate a Java interface based on your AIDL file. This interface includes an abstract Stub class that you will extend to implement your service.

Implementing the Custom Service

Now, let’s implement the actual logic for our IMyCustomService. We’ll integrate this into the system_server process for simplicity, which is where most core system services reside. Create a new Java file, for instance, frameworks/base/services/core/java/com/android/server/MyCustomService.java.

package com.android.server; import android.content.Context; import android.os.IMyCustomService; import android.os.RemoteException; import android.util.Slog; public class MyCustomService extends IMyCustomService.Stub {    private static final String TAG = "MyCustomService";    private final Context mContext;    public MyCustomService(Context context) {        mContext = context;        Slog.i(TAG, "MyCustomService instantiated.");    }    @Override    public int add(int a, int b) throws RemoteException {        Slog.d(TAG, "add(" + a + ", " + b + ") called.");        int result = a + b;        Slog.d(TAG, "Result: " + result);        return result;    }}

Explanation:

  • We extend IMyCustomService.Stub, which is the generated abstract class from our AIDL.
  • The constructor takes a Context object, which is useful for accessing other system services or resources.
  • We override the add method, providing our logic and using Slog for system-level logging.

Registering the Service with ServiceManager

For your service to be discoverable and usable by other components, it must be registered with the ServiceManager. The ServiceManager acts as a central registry for all binder services in the system.

We will register our service during the system’s boot process by modifying SystemServer.java. Locate frameworks/base/services/java/com/android/server/SystemServer.java and find the startOtherServices() method. Add your service instantiation and registration there:

// In SystemServer.java, within startOtherServices() try {    Slog.i(TAG, "Starting MyCustomService...");    ServiceManager.addService(Context.MY_CUSTOM_SERVICE, new MyCustomService(context));    Slog.i(TAG, "MyCustomService started.");} catch (Throwable e) {    Slog.e(TAG, "Failure starting MyCustomService", e);}

You’ll also need to define Context.MY_CUSTOM_SERVICE. Add the following constant to frameworks/base/core/java/android/content/Context.java:

// In Context.java, add this public static final String MY_CUSTOM_SERVICE = "my_custom_service";

Building and Flashing the Custom AOSP ROM for Emulator

With the service defined and implemented, it’s time to build your custom AOSP image and run it in an emulator.

Select a Target and Build

Choose an emulator target (e.g., aosp_emu-userdebug):

lunch aosp_emu-userdebug

Now, build the entire AOSP source. This will take considerable time depending on your system’s specifications:

make -j$(nproc)

Run the Emulator

Once the build completes successfully, the necessary images (system.img, userdata.img, ramdisk.img, etc.) will be located in out/target/product/generic_x86_64 (or your specific target directory). Launch the emulator with your newly built images:

emulator -writable-system -partition-size 4096 -selinux permissive -no-window -verbose &

The -writable-system flag allows adb remount later if needed, -partition-size ensures enough space, and -selinux permissive can help avoid SELinux issues during initial testing (though proper SELinux policies are critical for production).

Creating a Client Application to Test the Service

To verify your service works, you need an Android application that can connect to it. Create a new Android Studio project.

Include AIDL in Client App

Copy your IMyCustomService.aidl file from frameworks/base/core/java/android/os/ into your client app’s src/main/aidl/android/os/ directory. Android Studio will automatically generate the Java interface for you.

Connect to the Service

In your client application’s main activity (e.g., MainActivity.java), you can connect to and interact with your custom service:

package com.example.myclientservice; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.IBinder; import android.os.IMyCustomService; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity {    private static final String TAG = "MyClientApp";    private IMyCustomService mService;    private TextView mResultTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mResultTextView = findViewById(R.id.resultTextView);        Button callServiceButton = findViewById(R.id.callServiceButton);        callServiceButton.setOnClickListener(v -> callMyCustomService());        // Get the service binder        IBinder b = ServiceManager.getService("my_custom_service");        if (b != null) {            mService = IMyCustomService.Stub.asInterface(b);            Log.d(TAG, "Connected to MyCustomService.");        } else {            Log.e(TAG, "Failed to get MyCustomService.");            mResultTextView.setText("Error: Service not found!");        }    }    private void callMyCustomService() {        if (mService != null) {            try {                int a = 10;                int b = 20;                int result = mService.add(a, b);                mResultTextView.setText("Result of " + a + " + " + b + ": " + result);                Log.d(TAG, "Service call successful. Result: " + result);            } catch (RemoteException e) {                Log.e(TAG, "RemoteException calling service", e);                mResultTextView.setText("Error: RemoteException");            }        } else {            Log.e(TAG, "Service not available.");            mResultTextView.setText("Error: Service not available");        }    }}

Ensure your activity_main.xml has a TextView with id=

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner