文章目录
前言
越来越多的客户希望在开发三方app的时候,又要获取一般app无法获取的系统权限。这个时候就需要系统层提供接口给app调用,常规的方式就是将jar包或者aidl文件打包给客户,放在app中使用。但是,如今越来越多的需求修改一些需要系统权限的东西,如MDM之类的,更直接的是在系统进程中直接创建服务,这样做可能有以下问题:
1.安全性:当这个服务在产生crash时,系统进程一旦crash就会表现为手机重启,该现象容易和kernel层引起的KE产生的手机重启混淆(开机动画反复播放),加大排查难度。
2. 耦合性:不好使用宏控,代码层面还好用宏控制,不过一些附带的资源文件,比如drawable、layout等不好控制,即使同平台其它项目没有用到该功能,不过资源文件依然会占用掉系统的资源。但是倘若把服务逻辑和资源都放在app里面,岂不是就可以通过宏控决定?
以下是总结了创建以内置app形式添加的系统服务模板。
一、概述
以下提供的模板中:
XXX,Xxx,xxx都为自定义,自己可以随意定义,只要大小写的地方要统一
var function():函数名,请根据实际需要修改
二、在配置文件中定义宏控
目的是增加一个开关,不同项目决定是否编译该APP
1.开宏,device/项目目录/ProjectConfig.mk //路径根据平台而定
TEST_XXX_MANAGER = yes
- 宏控逻辑,控制app和服务模块, common.mk
ifeq ($(strip TEST_XXX_MANAGER yes)
PRODUCT_PACKAGES += Xxx
PRODUCT_PROPERTY_OVERRIDES += ro.test.xxx_manager = true //属性根据你平台来定,注意有些平台可能定义这个属性后开机获取不到
endif
三、在framework添加aidl
1.将aidl加入编译: frameworks/base/Android.mk//根据平台、Android版本不同也有可能是Android.bp,也可能不用配置,请灵活应对
...
core/java/com/test/xxx/IXxx.aidl \
...
2.注册服务, frameworks/base/core/java/android/app/SystemServiceRegistry.java
...
import com.test.xxx.XxxFeatureOption;
import com.test.xxx.XxxManager;
import com.test.xxx.XxxUtils;
...
static {
...
if (XxxFeatureOption.TEST_XXX_MANAGER) {
registerService(XxxUtils.SERVICE, XxxManager.class, new CachedServiceFetcher<XxxManager>() {
@Override
public XxxManager createService(ContextImpl ctx) {
return new XxxManager(ctx);
}
});
}
...
3.添加aidl文件, frameworks/base/core/java/com/test/xxx/IXxx.aidl
package com.test.xxx;
/**
* @hide
*/
interface IXxx {
var function();
}
4.此文件用来读取是否加服务,通过TEST_XXX_MANAGER判断, frameworks/base/core/java/com/test/xxx/XxxFeatureOption.java
package com.test.xxx;
import android.os.SystemProperties;
public class XxxFeatureOption {
public static final boolean TEST_XXX_MANAGER = SystemProperties.getBoolean(“ro.test.xxx_manager”, false);
}
5.添加aidl对应的manager,其中manager可以获取对应的服务 frameworks/base/core/java/com/test/xxx/XxxManager.java
package com.test.xxx;
import android.app.ActivityThread;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Singleton;
public final class XxxManager {
private static final String TAG = "XxxManager";
private static boolean sIsInitialized = false;
/**
* @hide
*/
public XxxManager(Context context) {
context = context.getApplicationContext();
if (context == null) {
throw new IllegalArgumentException("context not associated with any application (using a mock context?)");
}
try {
if (!sIsInitialized) {
getService();
sIsInitialized = true;
}
} catch (UnsupportedOperationException e) {
Log.e(TAG, "WL_DEBUG XxxManager e = " + e, e);
}
}
public static final XxxManager getInstance() {
Context context = ActivityThread.currentApplication();
if (context == null) {
throw new IllegalArgumentException("context cannot be null");
}
context = context.getApplicationContext();
if (context == null) {
throw new IllegalArgumentException("context not associated with any application (using a mock context?)");
}
/* use getSystemService() for consistency */
XxxManager manager = (XxxManager) context.getSystemService(XxxUtils.SERVICE);
return manager;
}
private static IXxx getService() {
return IXxxSingleton.get();
}
private static final Singleton<IXxx> IXxxSingleton = new Singleton<IXxx>() {
@Override
protected IXxx create() {
IBinder b = ServiceManager.getService(XxxUtils.SERVICE);
if (b == null) {
Log.e(TAG, "WL_DEBUG create failed", new Throwable());
return null;
}
return IXxx.Stub.asInterface(b);
}
};
public var function() {
var result = null;
try {
result = getService.function();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return result;
}
}
6.此文件用来保存服务名称和app包名等字符串,方便调用: frameworks/base/core/java/com/test/xxx/XxxUtils.java
package com.test.xxx;
import java.util.List;
import android.app.ActivityThread;
import android.app.Application;
public class XxxUtils {
public static final String SERVICE = "test_xxx";
public static final String PACKAGE = "com.test.xxx";
}
7.将com/test/xxx加到package_allowed_list.txt中
需要将在frameworks下新增的路径加到package_allowed_list.txt,否则会编译报错。
build/soong/scripts/check_boot_jars/package_allowed_list.txt
+com\.test\.xxx
四、app模板
app目录/Xxx/Android.mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := optional
LOCAL_PACKAGE_NAME := Xxx
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
app目录/Xxx/AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.xxx"
android:sharedUserId="android.uid.system"> -------------- 有了这句话,你就有了系统权限,绝大部分permission都不用声明
<application
android:name=".XxxApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:persistent="true"> -------------- 有了这句话,你的进程就永远存在,开机就唤醒,比谁都早,谁都杀不死你
<!--这里写你自己的代码-->
</application>
</manifest>
app目录/Xxx/res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Xxx</string>
<!--这里写你自己的代码-->
</resources>
app目录/Xxx/src/com/test/xxx/XxxApplication.java
package com.test.xxx;
import java.util.Iterator;
import java.util.List;
import android.app.ActivityManager;
import android.app.Application;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.os.Process;
public class XxxApplication extends Application {
private static final String TAG = "XxxApplication";
private XxxService mXxxService;
// private XxxYyyManager mXxxYyyManager;//按需要创建你自己的M
@Override
public void onCreate() {
super.onCreate();
boolean isMainProcess = false;
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List processes = am.getRunningAppProcesses();
Iterator i = processes.iterator();
while (i.hasNext()) {
RunningAppProcessInfo appInfo = (RunningAppProcessInfo) (i.next());
if (appInfo.pid == Process.myPid()) {
isMainProcess = (XxxUtils.PACKAGE.equals(appInfo.processName));
break;
}
}
if (UserHandle.myUserId() == 0 && isMainProcess) {
mXxxService = new XxxService(this);
// mXxxYyyManager = new XxxYyyManager(this);
//这里写你自己的代码,用于初始化代码,建议基于MVC的模式编辑,XxxApplication充当C(Control)的角色,相同模块的代码大于一行建议封装成一个M(Model),命名可以XxxYyyManager的形式命名,构造函数以public XxxYyyManager(XxxApplication app)与C交互,各M之间不建议直接交互
}
}
public var function() {
var result = ...;
...
return result;
}
}
app目录/Xxx/src/com/test/xxx/XxxService.java
package com.test.xxx;
import android.os.ServiceManager;
public class XxxService extends IXxx.Stub {
private final XxxApplication mApp;
public XxxService(XxxApplication xxxApplication) {
mApp = xxxApplication;
ServiceManager.addService(XxxUtils.SERVICE, this);
}
@Override
public var function() {
return mApp.function();
}
}
app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java//可选,如果你的功能需要提供给客户使用的,就提供这个文件就行,告诉客户通过XxxManager.getInstance获取对象,然后通过获取到的对象执行各个函数即可,该文件不参与系统编译,所有接口全部留空,无需实现
package com.test.xxx;
public final class XxxManager {
public static final XxxManager getInstance() {
return null;
}
public var function() {
return null;
}
}
五、为服务和app添加selinux权限
适用于:Android P及以上版本,sepolicy相关文件保证最后一个字符是回车换行,不然会出现异常
1.新建一个te文件来给新增加的app构建一个运行所在的domain,如test_xxx_app.te
device/…/sepolicy/…/test_xxx_app.te//各平台位置有细微差异,请灵活应对
type test_xxx_app, domain; //为app添加domain
typeattribute test_xxx_app coredomain, mlstrustedsubject; //给定核心域,后面的mlstrustedsubject可选,用于需要给客户提供app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java文件的项目
app_domain(test_xxx_app) //注意不用分号,给app应用域权限
net_domain(test_xxx_app) //注意不用分号,给app网络域权限
add_service(test_xxx_app, test_xxx_service) //把新增的服务关联到app,相当于add
allow test_xxx_app activity_service:service_manager find; //允许app访问ActivityService服务,不然会报avc错
2.给priv_app目录下的app访问新增服务的权限,因为我们的app就是放在这个目录 device/…/sepolicy/…/priv_app.te
...
allow priv_app test_xxx_service:service_manager find;
...
3.给新增的服务添加 系统服务进程 的权限, device/…/sepolicy/…/system_server.te
...
allow system_server test_xxx_service:service_manager find;
...
4.如果是暴露接口给三方用,则需要加这个权限 device/…/sepolicy/…/untrusted_app_all.te//可选,用于需要给客户提供app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java文件的项目
...
allow untrusted_app_all test_xxx_service:service_manager find;
...
- 给新增的服务定义域,给上下文, device/…/sepolicy/…/service_contexts
...
test_xxx u:object_r:test_xxx_service:s0
...
6.给新增的服务定义类型,类型为 service_manager_type, device/…/sepolicy/…/service.te
...
type test_xxx_service, service_manager_type, mlstrustedobject;
...
7.将新增的app和对应的domain关联起来, device/…/sepolicy/…/seapp_contexts
...
user=system seinfo=platform name=com.test.xxx domain=test_xxx_app type=app_data_file levelFrom=user
...
五、添加接口白名单(可选)
给第三方调用的话必须添加,否则可以不用。
frameworks/base/config/hiddenapi-greylist.txt//可选,用于需要给客户提供app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java文件的项目
...
Lcom/test/xxx/XxxManager;->getInstance()Lcom/test/xxx/XxxManager;
Lcom/test/xxx/XxxManager;->function()Lvar; ----------------请根据实际情况修改
按jni接口格式写,若不知道怎么写可以先编译,然后在生成的out\soong\hiddenapi中找到相应接口复制过来即可
Android P及以上版本:
frameworks/base/config/hiddenapi-greylist.txt//可选,用于需要给客户提供app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java文件的项目
Android S及以上版本:
frameworks/base/boot/hiddenapi/hiddenapi-unsupported.txt
若没有添加白名单,三方应用在通过XxxManager调用接口时,会出现如下报错:
PS:调试手段
adb shell下,敲ps -A | grep “关键词”,查查加的app进程运行了没有,以下结果是app在运行中:
K2-35:/ # ps -A | grep “test”
system 2493 590 14737064 87036 do_epoll_wait 0 S com.test.xxx
adb shell下,敲service list | grep “关键词”,查查app中服务add进去了没有,以下是在机器上adb下的显示结果:
K2-35:/ # service list | grep “test”
7 test_xxx: [com.test.xxx.IXxx]
其中“test_xxx”正是你在frameworks/base/core/java/android/app/SystemServiceRegistry.java注册服务的名字
总结
以上总结了如何添加一个完整的服务的模板,值示例了一个函数:var function():函数名
若要增加接口,则仿照该接口添加即可,需要同时修改以下文件:
frameworks/base/core/java/com/test/xxx/XxxManager.java
frameworks/base/core/java/com/test/xxx/IXxx.aidl
app目录/Xxx/src/com/test/xxx/XxxService.java
app目录/Xxx/src/com/test/xxx/XxxApplication.java
app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java//可选,如果你的功能需要提供给客户使用的,就提供这个文件就行,告诉客户通过XxxManager.getInstance获取对象,然后通过获取到的对象执行各个函数即可,该文件不参与系统编译,所有接口全部留空,无需实现
最后,若是提供接口给客户的三方普通app使用:
1、提供
app目录/Xxx/src_for_user/com/test/xxx/XxxManager.java该文件给客户,XxxManager.getInstance获取对象,然后通过获取到的对象执行各个函数即可,
注意XxxManager位于应用中的位置包路径要为com.test.xxx.XxxManager,与framework下添加的XxxManager路径一致
2、使用方法:
import com.test.xxx.XxxManager;
XxxManager mXxxManager = XxxManager.getInstance();
mXxxManager.function();