Last edited 3 years ago

How to add a custom service for Android

This article explains how to create your own Android service to manage a dedicated hardware resource. It is intended for distribution package users.

AndroidCustomService.png

1. Prerequisites[edit source]

The environment used must be installed using the right Distribution Package for your selected microprocessor device. See Distribution_Package.

To be able to execute following instructions you need to work from your distribution root directory and initialize your environment:

 source build/envsetup.sh
 lunch aosp_<BoardId>-userdebug

2. Hardware service[edit source]

First of all you need to create your HIDL files to be able to interact with your hardware. They act as the interface between your hardware and Android. You can refer to the HIDL manual [1]. For more information on HAL definition you can refer to the official AOSP documentation[2].

2.1. Creation[edit source]

To create your interface you need to create a folder myinterface in source tree path hardware/interfaces. Inside the interfaces folder, create a version folder for your interface like hardware/interfaces/myinterface/1.0/.

2.2. Hardware Interface (HIDL)[edit source]

Define as many classes and types you need to be able to interact with your hardware. Those functions must be implemented in your device HAL folder. (See Implement the vendor HAL)

IMyClass.hal

package android.hardware.myinterface@1.0;

interface IMyClass {
    myFunct1() generates (bool ret);
    myFunct2(string name) generates (bool ret, MyType val);
};

types.hal

package android.hardware.myinterface@1.0;

struct MyType {
    uint32_t id;
    bool state;
};

2.3. Generation[edit source]

Android.bp[3]:

hidl_interface {
    name: "android.hardware.myinterface@1.0",
    root: "android.hardware",
    vndk: {
        enabled: true,
    },
    srcs: [
        "types.hal",
        "IMyClass.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    types: [
        "MyType",
    ],
    gen_java: true,
}

2.4. Hash creation[edit source]

This step is not required to make it work but it must be done at least once when the interfaces are finalized nearing the end of development. When your interface is done you must execute

hidl-gen -L hash android.hardware.myinterface@1.0 >> hardware/interfaces/current.txt

This adds a specific hash matching your hardware interface. This ensures the use of the appropriate interface by the framework. Do not forget to update the hash if you make changes to the interface.

3. Service (AIDL)[edit source]

In order to interact with your hardware from an application you need to add your API on top of the already existing Android API.

3.1. Creation[edit source]

To create your API you need to create a "myinterface" subfolder at frameworks/base/core/java/android/.

3.2. Interface (AIDL)[edit source]

Create as many classes you need to define your API. All files need to have the .aidl extension. For more information on how to create AIDL files, see[4].

frameworks/base/core/java/android/myinterface/IMyClass.aidl

package android.myinterface;

interface IMyClass {
    void myAidlFunct1();
}

You need then to generate Java classes matching your AIDL files. Those functions will be the ones exposed to the application developer.

frameworks/base/core/java/android/myinterface/MyClassManager.java

package android.myinterface;

import android.util.AndroidException;
import android.util.Log;
import android.util.Slog;
import android.content.Context;
import android.os.RemoteException;

public class MyClassManager {
    private final Context mContext;
    private final IMyClass mService;

    public MyClassManager(Context ctx, IMyClass service) {
        mContext = ctx;
        mService = service;
    }

    public void myAidlFunct1() {
        try{
            mService.myAidlFunct1();
        } catch (RemoteException ex){
            Slog.e("MyClassManager", "Unable to contact the remote MyClassManager");
        }
    }
}

3.3. Generation[edit source]

To integrate your changes you need to adapt the Android.bp file in framework/base. Include all the AIDL files you have created beforehand.

java_library {
	.
	.
	.
    "core/java/android/myinterface/IMyClass.aidl",
}

3.4. Update API[edit source]

Add the following line in build/make/target/product/vndk/<current api version>.txt (ex: 28.txt for API 28) and in build/make/target/product/vndk/current.txt

 VNDK-core: android.hardware.myinterface@1.0.so

When your interface is well defined you need to execute the command:

 make update-api

This command updates all the necessary files to integrate your API in the distribution.

4. Implement the vendor HAL[edit source]

To implement the HAL you have just created please refer to the AOSP documentation[5].

5. Custom Service[edit source]

To be able to connect everything together you need to create an independent service that will run in background and reply to API calls.

5.1. Package services[edit source]

Create a folder in packages/services with the name of your service (MyInterfaceService)

prepare the folder structure as follows:

  • AndroidManifest.xml
  • Android.mk
  • src
    • com
      • android
        • myinterfaceservice
          • MyInterfaceServiceApp.java
          • IMyClassImpl.java

5.2. Implement service[edit source]

MyInterfaceServiceApp.java

package com.android.myinterfaceservice;

import android.util.Log;
import android.util.Slog;
import android.os.IBinder;

import android.app.Application;
import android.os.ServiceManager;

import android.myinterface.IMyClass;

public class MyInterfaceServiceApp extends Application { 
    private static final String REMOTE_SERVICE_NAME = IMyClass.class.getName();
    private static final String TAG = "MyInterfaceServiceApp";
    private IBinder mMyClassManager = null; 

    public void onCreate() {
        super.onCreate();
        Slog.d(TAG, "Build service");
        mMyClassManager = new IMyClassImpl();
        ServiceManager.addService(REMOTE_SERVICE_NAME, mMyClassManager);
    }

    public void onTerminate() {
        super.onTerminate();
    }
}

IMyClassImpl.java

package com.android.myinterfaceservice;

import android.util.Log;
import android.util.Slog;

import java.util.ArrayList;
import android.os.RemoteException;

import android.myinterface.IMyClass;

import android.hardware.myinterface.V1_0.IMyClass;

class IMyClassImpl extends IMyClass.Stub {
    private static final String TAG = "IMyClassImpl";
    private IMyClass mIMyClassHal = null;

    public IMyClassImpl() {
        Slog.d(TAG, "Build service");
        try {
            mIMyClassHal = IMyClass.getService();
        } catch (Exception e)
        {
            mIMyClassHal = null;
        }
    }

    @Override
    public void myAidlFunct1() throws RemoteException {
        if (mIMyClassHal != null) {
            mIMyClassHal.myFunct1(new IMyClass.myFunct1Callback() {

                @Override
                public void onValues(bool ret) {
                    //Function that you have to treat the HAL "IMyClass myFunct1() generates (bool ret)" returned value.
                }
            });
        }
    }

5.3. Makefile and manifest[edit source]

Android.mk

 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := optional
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_REQUIRED_MODULES := android.hardware.myinterface
 LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.myinterface-V1.0-java
 LOCAL_PACKAGE_NAME := MyInterfaceService
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_CERTIFICATE := platform
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_PRIVATE_PLATFORM_APIS := true
 include $(BUILD_PACKAGE)

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.android.myinterfaceservice"
  android:sharedUserId="android.uid.system">
<application android:name=".MyInterfaceServiceApp"
             android:persistent="true">
 </application>
</manifest>

6. Add SEpolicy[edit source]

To be able to execute your service, even if your system is permissive you need to declare dedicated SEpolicy.

Because the new service is embedded like any other AOSP service you need to adapt official AOSP SEpolicy.

For more details please refer to the official AOSP documentation[6].

6.1. Change AOSP SEpolicy[edit source]

In system/sepolicy/public/attributes, you need to declare a new HAL name.

hal_attribute(myservice);

This creates all necessary rules for the HAL.

In system/sepolicy/public/service.te

type myservice_service, app_api_service, service_manager_type;

This creates the new SEpolicy name with appropriate rights.

In system/sepolicy/private/service_contexts

 android.myinterface.IMyClass               u:object_r:myservice_service:s0

This gives the correct rights to the service.

In system/sepolicy/private/seapp_contexts add the new packages/apps rules.

 user=system seinfo=myservice name=com.android.myinterfaceservice domain=myservice_app type=app_data_file

This gives the correct rights to the packages/apps service.

In system/sepolicy/private/compat/27.0/27.0.ignore.cil and system/sepolicy/private/compat/26.0/26.0.ignore.cil add the service at the end of the list.

 myservice_service

Because of the base AOSP SEpolicy change you need to copy those files in the prebuilt location (path depends on API version used)

 cp system/sepolicy/public/attributes system/sepolicy/prebuilts/api/28.0/public/attributes
 cp system/sepolicy/public/service.te system/sepolicy/prebuilts/api/28.0/public/service.te
 cp system/sepolicy/private/service_contexts system/sepolicy/prebuilts/api/28.0/private/service_contexts
 cp system/sepolicy/private/seapp_contexts system/sepolicy/prebuilts/api/28.0/private/seapp_contexts
 cp system/sepolicy/private/compat/27.0/27.0.ignore.cil system/sepolicy/prebuilts/api/28.0/private/compat/27.0/27.0.ignore.cil
 cp system/sepolicy/private/compat/26.0/26.0.ignore.cil system/sepolicy/prebuilts/api/28.0/private/compat/26.0/26.0.ignore.cil

6.2. Add custom SEpolicy to distribution[edit source]

Create device/stm/<STM32Series>/sepolicy/hal_myinterface_default.te

type hal_myservice_default, domain;
hal_server_domain(hal_myservice_default, hal_myservice)

type hal_myservice_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_myservice_default)

binder_call(hal_myservice_client, hal_myservice_server)
binder_call(hal_myservice_server, hal_myservice_client)

add_hwservice(hal_myservice_server, hal_myservice_hwservice)

If your HAL needs to access some specific system files you need to add the definition here.

In 'device/stm/<STM32Series>/sepolicy/myservice_app.te

#packages app MyInterfaceService
type myservice_app, domain;

7. Add to the board[edit source]

To include your new service in the board-specific Android image you need to customize your device.mk.

PRODUCT_PACKAGES += \
        android.hardware.myinterface@1.0-service.my \ # The HAL
        MyInterfaceService # The packages apps service

You need to rebuild your distribution to integrate the changes.

 make -j

8. SDK integration[edit source]

To allow using the created service in your applications, you have to re-generate the SDK and install it within Android Studio (refer to How to build and install an SDK for Android).

9. References[edit source]