Introduction to AAOS Custom Car Services
Android Automotive OS (AAOS) is a full-stack, open-source platform powering the infotainment systems in modern vehicles. While AAOS provides a robust set of core services through its `CarService` framework (e.g., HVAC, Radio, VHAL), there will inevitably be scenarios where vehicle manufacturers or third-party developers need to expose custom functionalities specific to their hardware or unique vehicle features. This is where custom Car Services come into play.
A custom Car Service in AAOS operates similarly to a standard Android bound service but is specifically designed to interact with vehicle-specific hardware or software components, often running with elevated privileges within the system partition. This tutorial will guide you through building your very first custom Car Service from the ground up, covering everything from AIDL definition to client interaction and deployment.
Prerequisites
- An AAOS development environment (e.g., AOSP build targeting a Cuttlefish emulator or a physical AAOS device).
- Android Studio for developing the service and client applications.
- Basic understanding of Android Service lifecycle and Inter-Process Communication (IPC) using AIDL.
- `adb` command-line tool configured for your AAOS device/emulator.
Step 1: Define the AIDL Interface
AIDL (Android Interface Definition Language) is crucial for defining the interface that clients will use to interact with your custom Car Service. It allows processes to communicate with each other through IPC. For our example, let’s create a simple service that provides a custom message and a counter.
Create a new Android Library module (or within your main app module if it’s a system app) named `customcarservice_aidl`. Inside this module, define your AIDL file. In Android Studio, right-click on `app/src/main` > `New` > `AIDL` > `AIDL File` and name it `ICustomCarService`.
// ICustomCarService.aidl
package com.example.customcarservice;
interface ICustomCarService {
String getCustomMessage();
int incrementAndGetCounter();
void registerCallback(ICustomCarServiceCallback callback);
void unregisterCallback(ICustomCarServiceCallback callback);
}
Now, let’s define the callback interface for asynchronous communication:
// ICustomCarServiceCallback.aidl
package com.example.customcarservice;
oneway interface ICustomCarServiceCallback {
void onCounterChanged(int newCounterValue);
}
Build the project once to generate the Java interfaces from these AIDL files.
Step 2: Implement the Custom Car Service
Now, we’ll create the actual Android Service that implements the `ICustomCarService` interface. Create a new Android Application module (e.g., `CustomCarServiceApp`) in your project.
2.1 Add AIDL dependency
First, ensure your `CustomCarServiceApp` module depends on your AIDL library. In `build.gradle` for `CustomCarServiceApp`:
dependencies {
implementation project(':customcarservice_aidl')
}
2.2 Create the Service Class
Create a new Java class `CustomCarService` extending `android.app.Service`.
package com.example.customcarserviceapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import com.example.customcarservice.ICustomCarService;
import com.example.customcarservice.ICustomCarServiceCallback;
import java.util.concurrent.atomic.AtomicInteger;
public class CustomCarService extends Service {
private static final String TAG = "CustomCarService";
private AtomicInteger mCounter = new AtomicInteger(0);
private final RemoteCallbackList<ICustomCarServiceCallback> mCallbacks = new RemoteCallbackList<>();
private final ICustomCarService.Stub mBinder = new ICustomCarService.Stub() {
@Override
public String getCustomMessage() throws RemoteException {
Log.d(TAG, "getCustomMessage() called");
return "Hello from Custom AAOS Car Service!";
}
@Override
public int incrementAndGetCounter() throws RemoteException {
int newCount = mCounter.incrementAndGet();
Log.d(TAG, "incrementAndGetCounter() called, newCount: " + newCount);
notifyCounterChanged(newCount);
return newCount;
}
@Override
public void registerCallback(ICustomCarServiceCallback callback) throws RemoteException {
if (callback != null) {
mCallbacks.register(callback);
Log.d(TAG, "Callback registered.");
}
}
@Override
public void unregisterCallback(ICustomCarServiceCallback callback) throws RemoteException {
if (callback != null) {
mCallbacks.unregister(callback);
Log.d(TAG, "Callback unregistered.");
}
}
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind() called with intent: " + intent);
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() called.");
}
@Override
public void onDestroy() {
super.onDestroy();
mCallbacks.kill();
Log.d(TAG, "onDestroy() called.");
}
private void notifyCounterChanged(int newCounterValue) {
int i = mCallbacks.beginBroadcast();
while (i > 0) {
i--;
try {
mCallbacks.getBroadcastItem(i).onCounterChanged(newCounterValue);
} catch (RemoteException e) {
Log.e(TAG, "Error notifying callback", e);
}
}
mCallbacks.finishBroadcast();
}
}
2.3 Declare Service in AndroidManifest.xml
Open `AndroidManifest.xml` in `CustomCarServiceApp` and declare your service. It’s crucial to add an intent filter with a unique action that clients can use to bind to your service. We’ll also add a custom permission for binding, enhancing security.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.customcarserviceapp">
<permission android:name="com.example.customcarservice.BIND_CUSTOM_CAR_SERVICE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.customcarservice.BIND_CUSTOM_CAR_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CustomCarServiceApp">
<service
android:name=".CustomCarService"
android:exported="true">
<intent-filter>
<action android:name="com.example.customcarservice.BIND"
/>
</intent-filter>
</service>
</application>
</manifest>
Note the `android:exported=”true”` and the custom permission with `protectionLevel=”signature”`. This means only apps signed with the same certificate as your service can bind to it. For system apps, `signature` protection is common. For simpler debugging or if installed as a user app, you might temporarily use `normal` protection, but be aware of security implications.
Step 3: Build and Deploy the Service APK
Build the `CustomCarServiceApp` module in Android Studio to generate the APK. Once built, deploy it to your AAOS emulator or device using ADB.
adb install path/to/CustomCarServiceApp/build/outputs/apk/debug/CustomCarServiceApp-debug.apk
If you’re deploying to a read-only system partition (common for custom car services meant to be part of the system image), you might need to push it to `/system/priv-app/` or `/system/app/` after remounting the system partition as writable and rebooting. For development, `adb install` to `/data/app` is usually sufficient.
Step 4: Create a Client Application
Now, let’s create a separate Android Application module (e.g., `CustomCarServiceClient`) that will bind to and interact with our custom service.
4.1 Add AIDL dependency
Similar to the service app, the client also needs access to the AIDL interfaces. In `build.gradle` for `CustomCarServiceClient`:
dependencies {
implementation project(':customcarservice_aidl')
}
4.2 Client Code to Bind and Interact
Create a simple UI (e.g., `MainActivity.java` with a few buttons and TextViews) to display the message and counter. The core logic will be in `MainActivity`.
package com.example.customcarserviceclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.customcarservice.ICustomCarService;
import com.example.customcarservice.ICustomCarServiceCallback;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "CustomClient";
private ICustomCarService mService;
private boolean mBound = false;
private TextView messageTextView;
private TextView counterTextView;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "Service Connected");
mService = ICustomCarService.Stub.asInterface(service);
mBound = true;
try {
String message = mService.getCustomMessage();
messageTextView.setText("Service Message: " + message);
mService.registerCallback(mCallback);
int currentCounter = mService.incrementAndGetCounter(); // Get initial counter
counterTextView.setText("Counter: " + currentCounter);
} catch (RemoteException e) {
Log.e(TAG, "Error calling service method", e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "Service Disconnected");
mService = null;
mBound = false;
messageTextView.setText("Service Message: Disconnected");
counterTextView.setText("Counter: Disconnected");
}
};
private ICustomCarServiceCallback.Stub mCallback = new ICustomCarServiceCallback.Stub() {
@Override
public void onCounterChanged(int newCounterValue) throws RemoteException {
Log.d(TAG, "onCounterChanged: " + newCounterValue);
runOnUiThread(() -> counterTextView.setText("Counter: " + newCounterValue));
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
messageTextView = findViewById(R.id.messageTextView);
counterTextView = findViewById(R.id.counterTextView);
Button incrementButton = findViewById(R.id.incrementButton);
incrementButton.setOnClickListener(v -> {
if (mBound) {
try {
int newCount = mService.incrementAndGetCounter();
// UI update happens via callback now
} catch (RemoteException e) {
Log.e(TAG, "Error incrementing counter", e);
}
}
});
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent("com.example.customcarservice.BIND");
intent.setComponent(new ComponentName(
"com.example.customcarserviceapp",
"com.example.customcarserviceapp.CustomCarService"));
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
Log.e(TAG, "Error unregistering callback", e);
}
unbindService(mConnection);
mBound = false;
}
}
}
4.3 Client Manifest
In `AndroidManifest.xml` for `CustomCarServiceClient`, declare the permission to bind to the service.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.customcarserviceclient">
<uses-permission android:name="com.example.customcarservice.BIND_CUSTOM_CAR_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CustomCarServiceClient">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Step 5: Testing the Custom Car Service
1. **Install the client app:** Build `CustomCarServiceClient` and install it on your AAOS device/emulator.adb install path/to/CustomCarServiceClient/build/outputs/apk/debug/CustomCarServiceClient-debug.apk
2. **Launch the client:** Find the `CustomClient` app in the AAOS launcher and open it.
3. **Observe:** Upon launch, the client app should bind to your service. You should see the
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 →