In the previous post, we built a native daemon in Android native layer. We wrote a simple application to communicate with the daemon using a socket (UDS/UDP/TCP). Most of the services provided by Android using the Android Framewok layer

If for example, the camera application wants to get the current GPS location, it connects to the Location service using the binder. The service load the hardware module using the HAL and returns the position.

To create a system service, we need the following components:

AIDL service interface

Application to host the service(s)

Client library for untrusted applications (from the play store) to use the service

Simple client application for testing our service

AIDL and the Client Library

We start with the AIDL file that will generate the stub and proxy. We also build a Manager class for the client application.

Create a folder framework in ~/aosp/device/generic/goldfish

Add a general Android.mk file to scan the child directories

framework/Android.mk

include $(call all-subdir-makefiles) 1 include $ ( call all - subdir - makefiles )

Build the following directory structure:

ISampService.aidl

package com.android.sampservice; interface ISampService { int add(int a,int b); int sub(int a,int b); } 1 2 3 4 5 6 7 package com . android . sampservice ; interface ISampService { int add ( int a , int b ) ; int sub ( int a , int b ) ; }

SampManager.java

package com.android.sampservice; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.util.Slog; import java.util.HashSet; import java.util.Set; public class SampManager { private static final String REMOTE_SERVICE_NAME = ISampService.class.getName(); private final ISampService service; public static SampManager getInstance() { return new SampManager(); } public int add(int a,int b) { try { return service.add(a,b); }catch(Exception ec){} return 0; } public int sub(int a,int b) { try { return service.sub(a,b); }catch(Exception ec){} return 0; } private SampManager() { this.service = ISampService.Stub.asInterface( ServiceManager.getService(REMOTE_SERVICE_NAME)); if (this.service == null) { throw new IllegalStateException("Failed to find ISampService by name [" + REMOTE_SERVICE_NAME + "]"); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com . android . sampservice ; import android . os . Handler ; import android . os . IBinder ; import android . os . Message ; import android . os . RemoteException ; import android . os . ServiceManager ; import android . util . Log ; import android . util . Slog ; import java . util . HashSet ; import java . util . Set ; public class SampManager { private static final String REMOTE_SERVICE_NAME = ISampService . class . getName ( ) ; private final ISampService service ; public static SampManager getInstance ( ) { return new SampManager ( ) ; } public int add ( int a , int b ) { try { return service . add ( a , b ) ; } catch ( Exception ec ) { } return 0 ; } public int sub ( int a , int b ) { try { return service . sub ( a , b ) ; } catch ( Exception ec ) { } return 0 ; } private SampManager ( ) { this . service = ISampService . Stub . asInterface ( ServiceManager . getService ( REMOTE_SERVICE_NAME ) ) ; if ( this . service == null ) { throw new IllegalStateException ( "Failed to find ISampService by name [" + REMOTE_SERVICE_NAME + "]" ) ; } } }

Android.mk

LOCAL_PATH := $(call my-dir) # Build the library include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_MODULE := com.android.sampservice LOCAL_SRC_FILES := $(call all-java-files-under,.) LOCAL_SRC_FILES += com/android/sampservice/ISampService.aidl include $(BUILD_JAVA_LIBRARY) # Copy com.android.sampservice.xml to /system/etc/permissions/ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_MODULE := com.android.sampservice.xml LOCAL_MODULE_CLASS := ETC LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions LOCAL_SRC_FILES := $(LOCAL_MODULE) include $(BUILD_PREBUILT) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 LOCAL_PATH : = $ ( call my - dir ) # Build the library include $ ( CLEAR_VARS ) LOCAL_MODULE_TAGS : = eng LOCAL_MODULE : = com . android . sampservice LOCAL_SRC_FILES : = $ ( call all - java - files - under , . ) LOCAL_SRC_FILES += com / android / sampservice / ISampService . aidl include $ ( BUILD_JAVA_LIBRARY ) # Copy com.android.sampservice.xml to /system/etc/permissions/ include $ ( CLEAR_VARS ) LOCAL_MODULE_TAGS : = eng LOCAL_MODULE : = com . android . sampservice . xml LOCAL_MODULE_CLASS : = ETC LOCAL_MODULE_PATH : = $ ( TARGET_OUT_ETC ) / permissions LOCAL_SRC_FILES : = $ ( LOCAL_MODULE ) include $ ( BUILD_PREBUILT )

To let the untrusted application access the library we need to add a permission file:

com.android.sampservice.xml

<?xml version="1.0" encoding="utf-8"?> <permissions> <library name="com.android.sampservice" file="/system/framework/com.android.sampservice.jar"/> </permissions> 1 2 3 4 5 <? xml version = "1.0" encoding = "utf-8" ?> < permissions > < library name = "com.android.sampservice" file = "/system/framework/com.android.sampservice.jar" / > < / permissions >

The purpose of the permission file is to tell the system where to find the required library so if untrusted application wants to use the library, it should add <uses-library …. /> tag to AndroidManifest.xml file with the required name

The generated components are: jar file to access the service and xml file for permission

The Service Implementation

To build the service we need to implement the interface and write an application to host it. We can use the system_server but its a bad practice to merge our code with google’s code.

Create a folder app in ~/aosp/device/generic/goldfish

Add a general Android.mk file to scan the child directories

app/Android.mk

include $(call all-subdir-makefiles) 1 include $ ( call all - subdir - makefiles )

Build the following directory structure:

The service implementation

We need to derive from the Stub class and simply implement the interface

SampServiceApp/src/com/android/sampappservice/ISampServiceImpl.java

package com.android.sampappservice; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import com.android.sampservice.*; class ISampServiceImpl extends ISampService.Stub { public int add(int a,int b) { return a+b ; } public int sub(int a,int b) { return a-b; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com . android . sampappservice ; import android . os . Binder ; import android . os . IBinder ; import android . os . RemoteException ; import com . android . sampservice . * ; class ISampServiceImpl extends ISampService . Stub { public int add ( int a , int b ) { return a + b ; } public int sub ( int a , int b ) { return a - b ; } }

Now we create an application to host the service. We need to create an object from our implementation and add it to the Service Manager (the binder context)

SampServiceApp.java

package com.android.sampappservice; import android.app.Application; import android.os.ServiceManager; import android.util.Log; import com.android.sampservice.ISampService; public class SampServiceApp extends Application { private static final String REMOTE_SERVICE_NAME = ISampService.class.getName(); private ISampServiceImpl serviceImpl; public void onCreate() { super.onCreate(); this.serviceImpl = new ISampServiceImpl(); ServiceManager.addService(REMOTE_SERVICE_NAME, this.serviceImpl); } public void onTerminate() { super.onTerminate(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com . android . sampappservice ; import android . app . Application ; import android . os . ServiceManager ; import android . util . Log ; import com . android . sampservice . ISampService ; public class SampServiceApp extends Application { private static final String REMOTE_SERVICE_NAME = ISampService . class . getName ( ) ; private ISampServiceImpl serviceImpl ; public void onCreate ( ) { super . onCreate ( ) ; this . serviceImpl = new ISampServiceImpl ( ) ; ServiceManager . addService ( REMOTE_SERVICE_NAME , this . serviceImpl ) ; } public void onTerminate ( ) { super . onTerminate ( ) ; } }

To build a system application we need to update the AndroidManifest.xml file.

To make it run as system user add android:sharedUserId=”android.uid.system”

To make it run automatically on startup add android:persistent=”true”

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.sampappservice" android:sharedUserId="android.uid.system"> <application android:name=".SampServiceApp" android:persistent="true"> <uses-library android:name="com.android.sampservice" /> </application> </manifest> 1 2 3 4 5 6 7 8 <? xml version = "1.0" encoding = "utf-8" ?> < manifest xmlns : android = "http://schemas.android.com/apk/res/android" package = "com.android.sampappservice" android : sharedUserId = "android.uid.system" > < application android : name = ".SampServiceApp" android : persistent = "true" > < uses - library android : name = "com.android.sampservice" / > < / application > < / manifest >

The following Android.mk build the package (apk)

LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_REQUIRED_MODULES := com.android.sampservice LOCAL_JAVA_LIBRARIES := com.android.sampservice \ framework LOCAL_PACKAGE_NAME := SampServiceApp LOCAL_SDK_VERSION := current LOCAL_PROGUARD_ENABLED := disabled LOCAL_CERTIFICATE := platform LOCAL_PRIVILEGED_MODULE := true include $(BUILD_PACKAGE) 1 2 3 4 5 6 7 8 9 10 11 12 13 LOCAL_PATH : = $ ( call my - dir ) include $ ( CLEAR_VARS ) LOCAL_MODULE_TAGS : = eng LOCAL_SRC_FILES : = $ ( call all - java - files - under , src ) LOCAL_REQUIRED_MODULES : = com . android . sampservice LOCAL_JAVA_LIBRARIES : = com . android . sampservice \ framework LOCAL_PACKAGE_NAME : = SampServiceApp LOCAL_SDK_VERSION : = current LOCAL_PROGUARD_ENABLED : = disabled LOCAL_CERTIFICATE : = platform LOCAL_PRIVILEGED_MODULE : = true include $ ( BUILD_PACKAGE )

Note the platform certificate we need to sign the application (LOCAL_CERTIFICATE := platform , LOCAL_PRIVILEGED_MODULE := true)

Build and run the emulator – You will see that the service is not running and in the log you will see the following error:

01-01 00:02:44.148 1819 1819 E SELinux : avc: denied { add } for service=com.android.sampservice.ISampService pid=16277 uid=1000 scontext=u:r:system_app:s0 tcontext=u:object_r:my_service:s0 tclass=service_manager permissive=0 01-01 00:02:44.148 1819 1819 E ServiceManager: add_service('com.android.sampservice.ISampService',7b) uid=1000 - PERMISSION DENIED 01-01 00:02:44.150 16277 16277 D AndroidRuntime: Shutting down VM 01-01 00:02:44.151 16277 16277 E AndroidRuntime: FATAL EXCEPTION: main 01-01 00:02:44.151 16277 16277 E AndroidRuntime: Process: com.android.sampappservice, PID: 16277 01-01 00:02:44.151 16277 16277 E AndroidRuntime: java.lang.RuntimeException: Unable to create application com.android.sampappservice.SampServiceApp: java.lang.SecurityException 01-01 00:02:44.151 16277 16277 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5810) 01-01 00:02:44.151 16277 16277 E AndroidRuntime: at android.app.ActivityThread.-wrap1(Unknown Source:0) 1 2 3 4 5 6 7 8 01 - 01 00 : 02 : 44.148 1819 1819 E SELinux : avc : denied { add } for service = com . android . sampservice . ISampService pid = 16277 uid = 1000 scontext = u : r : system_app : s0 tcontext = u : object_r : my_service : s0 tclass = service_manager permissive = 0 01 - 01 00 : 02 : 44.148 1819 1819 E ServiceManager : add_service ( 'com.android.sampservice.ISampService' , 7b ) uid = 1000 - PERMISSION DENIED 01 - 01 00 : 02 : 44.150 16277 16277 D AndroidRuntime : Shutting down VM 01 - 01 00 : 02 : 44.151 16277 16277 E AndroidRuntime : FATAL EXCEPTION : main 01 - 01 00 : 02 : 44.151 16277 16277 E AndroidRuntime : Process : com . android . sampappservice , PID : 16277 01 - 01 00 : 02 : 44.151 16277 16277 E AndroidRuntime : java . lang . RuntimeException : Unable to create application com . android . sampappservice . SampServiceApp : java . lang . SecurityException 01 - 01 00 : 02 : 44.151 16277 16277 E AndroidRuntime : at android . app . ActivityThread . handleBindApplication ( ActivityThread . java : 5810 ) 01 - 01 00 : 02 : 44.151 16277 16277 E AndroidRuntime : at android . app . ActivityThread . - wrap1 ( Unknown Source : 0 )

We need to add SE Linux permissions:

SE Linux

Declare the new type my_service in public/service.te:

type my_service, system_api_service, service_manager_type; 1 type my_service , system_api_service , service_manager_type ;

In service_contexts file label the service

com.android.sampservice.ISampService u:object_r:my_service:s0 1 com . android . sampservice . ISampService u : object_r : my_service : s0

Add rule: (in public/servicemanager.te)

allow system_app my_service:service_manager add; 1 allow system_app my_service : service_manager add ;

In android 8.1 google added a new policy language called the Common Intermediate Language (CIL). To add a system service you need:

in file system/sepolicy/private/compat/26.0/26.0.cil

(typeattributeset my_service_26_0 (my_service)) 1 ( typeattributeset my_service_26_0 ( my_service ) )

in file /system/sepolicy/prebuild/api/26.0/nonplat_sepolicy.cil

(typeattribute my_service_26_0) (roletype object_r my_service_26_0) 1 2 ( typeattribute my_service_26_0 ) ( roletype object_r my_service_26_0 )

and add my_service to the line

(typeattributeset service_manager_type (audioserver_service_26_0 , ... , my_service_26_0 1 ( typeattributeset service_manager _ type ( audioserver_service_26 _ 0 , . . . , my_service_26_0

Build and run the emulator again, you will see the service running

Create a client Application

First we need to create a jar file for the application developer. The project in Android Studio depends on that library

In AOSP root, run the following command:

# make javac-check-com.android.sampservice 1 # make javac-check-com.android.sampservice

You will find the file classes-full-debug.jar in out/target/common/obj/JAVA_LIBRARIES/com.android.sampservice_intermediates/

Create a new Android Studio application, add module and select import jar

import the above jar file and click finish

In the application module settings add module dependency to the jar file module and select compile only

To use the library from the device we need to add <uses-library> tag to the manifest file:

<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/AppTheme"> <uses-library android:name="com.android.sampservice" android:required="true" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 < 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/AppTheme" > < uses - library android : name = "com.android.sampservice" android : required = "true" / > < activity android : name = ".MainActivity" > < intent - filter > < action android : name = "android.intent.action.MAIN" / > < category android : name = "android.intent.category.LAUNCHER" / > < / intent - filter > < / activity > < / application >

Now just create an object from the SampManager class and use it:

SampManager man = SampManager.getInstance(); Log.d("calling system service", "res:" + man.add(100,200)); 1 2 SampManager man = SampManager . getInstance ( ) ; Log . d ( "calling system service" , "res:" + man . add ( 100 , 200 ) ) ;

Last thing we need to add SE Linux rule to allow untrusted application. Add the following rule to the untrusted_app.te file

allow untrusted_app my_service:service_manager { find }; 1 allow untrusted_app my_service : service_manager { find } ;

Run the application on the emulator and use logcat to see the results