背景:应系统任务要求,在android设备开机 和 列表更新时 读取系统属性disable的应用列表,禁止任何地方启动该应用的Activity,并Toast 3秒提示:The app is blocked.
通过overlay配置进行控制是否启用该功能,轻量级,不影响系统功能。
此次修改是针对Android Q(SDK 29),也就是android 10来说的
一、熟悉Activity启动流程
- 首先从 Context.startActivity() 开始,调到 ContextImpl.startActivityForResult()
- 通过 Instrumentation 通过 AIDL 调用 ActivityTaskManagerService(也是AMS的一部分)的 startActivity() 方法,从应用进程调用到系统进程
- SystemServer 会有一连串的调用和重载,主要是对于 Intent 的解析(发生在 ActivityStarter)、任务栈的管理(发生在 ActivityStack 和 ActivityStackSupervisor)、进程的创建和调度(发生在 AMS) 等等
如果进程没有创建,会先走一遍进程创建的流程,并把这个启动 Activity 的任务悬挂起来,在进程创建好并报道之后回调生命周期
- 在保证进程已经创建好之后,通过 IApplicationThread 进行跨进程通讯,从系统进程回到应用进程,通过 ActivityThread 中的 Handler 接收到消息并在主线程处理
- 最后又通过 Instrumentation 反射创建要启动的 Activity,为其创建 ContextImpl,执行 attach 进行一些初始化操作,并调用创建的 Activity 的 onCreate() 回调生命周期
也就是说启动 Activity 其实是至少经过了两次跨进程通讯(App -> SystemServer -> App)才将 Activity 启动起来的。
值得注意的是
- Android Q 以后把很多 ActivityManagerService 的逻辑移到了 ActivityTaskManagerService
- IApplicationThread 是一个 oneway 的 .aidl 文件,说明在 SystemServer 端,调用客户端的方法是异步的,并不关心返回结果,而是设定一个超时时间等待客户端来报道
二、任务修改步骤
1.在frameworks层,打log找资料定位且有参数可以判断拦截startActivity的地方.摸索了大半天发现ActivityStarter.startActivityMayWait方法中加入拦截 .
ActivityTaskManagerService->>ActivityStarter:startActivityAsUser
ActivityStarter->>ActivityStarter:startActivityMayWait
2.读取系统服务写入Settings Global表中的字符串,对接字段名为disable_list 数据类型如"kr.co.captv.pooqcom.google.android.youtube.tv",无值时为"",注意还可能出现未写入disable_list字段的情况.
@@ -151,6 +148,36 @@ class ActivityStarter {
private final ActivityStartInterceptor mInterceptor;
private final ActivityStartController mController;
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {
+ private static final String NES_SKY_WAY_DISABLE_LIST = "disable_list";
+ private ArrayList<String> mNesDisableList;
+ private Boolean mSupportInterceptApp = false;
+ private ContentObserver mNesDisableContentObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ Slog.d(TAG, "status has changed, update disable list");
+ readNesDisableList();
+ }
+ };
+
+ private void readNesDisableList(){
+ try {
+ String listString = Settings.Global.getString(ActivityThread.currentApplication().getContentResolver(), NES_SKY_WAY_DISABLE_LIST);
+ if ((listString != null) && (!"".equals(listString.trim()))) {
+ mNesDisableList = new ArrayList(Arrays.asList(listString.split(";")));
+ Slog.d(TAG, "Sky way intercept application list: " + mNesDisableList);
+ return;
+ } else {
+ Slog.d(TAG, "Sky way disable list is null");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ mNesDisableList = new ArrayList<String>();
+ }
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps }
+
// Share state variable among methods when starting an activity.
private ActivityRecord mStartActivity;
private Intent mIntent;
3.开机和更新数据列表时,读取该列表,更新类中的数据表.分别在ActivityStarter类初始化做操作:读取和注册监听
@@ -444,6 +471,17 @@ class ActivityStarter {
mSupervisor = supervisor;
mInterceptor = interceptor;
reset(true);
+
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {
+ try {
+ readNesDisableList();
+ mSupportInterceptApp = ActivityThread.currentApplication().getResources().getBoolean(com.android.internal.R.bool.config_supportInterceptApplicationStartup);
+ ActivityThread.currentApplication().getContentResolver().registerContentObserver(Settings.Global.getUriFor(NES_SKY_WAY_DISABLE_LIST), false, mNesDisableContentObserver);
+ Slog.d(TAG, "ActivityStarter support intercept app: " + mSupportInterceptApp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps }
}
4.添加配置功能开关配置,在symbols.xml注册bool值config_supportInterceptApplicationStartup,在特定配置 overlay中设置该值为true
diff --git a/frameworks/base/core/res/res/values/symbols.xml b/frameworks/base/core/res/res/values/symbols.xml
+ <!-- // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {-->
+ <java-symbol type="bool" name="config_supportInterceptApplicationStartup" />
+ <!-- // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps }-->
diff --git a/frameworks/base/core/res/res/values/config.xml
+ <!-- // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {-->
+ <bool name="config_supportInterceptApplicationStartup">false</bool>
+ <!-- // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps }-->
5.拦截功能开启的条件为,config_supportInterceptApplicationStartup为true,启动的应用包名包含在拦截名单里.
@@ -1366,6 +1423,20 @@ class ActivityStarter {
}
}
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {
+ private boolean isSkyWayInterceptApplicationStartup(String packageName) {
+ try {
+ if (mSupportInterceptApp && (null != packageName) && (mNesDisableList.size() > 0)) {
+ return mNesDisableList.contains(packageName);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to intercept application startup: " + e);
+ e.printStackTrace();
+ }
+ return false;
+ }
+ // [SEI-lixj-2020-11-05] Skyway proh
6.拦截成功Toast"The app is blocked"提示用户,目前Toast时间长度设置为LENGTH_LONG.
@@ -1214,6 +1252,25 @@ class ActivityStarter {
// Collect information about the target of the Intent.
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps {
+ Slog.d(TAG, "callingPackage: " + callingPackage + ",launching: " + aInfo.packageName);
+ if (isSkyWayInterceptApplicationStartup(aInfo.packageName)) {
+ try {
+ Slog.d(TAG, "Successfully intercepted application launch: " + aInfo.packageName);
+ // the application's main looper, which lives in the main thread of the application.
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ public void run() {
+ Toast.makeText(ActivityThread.currentApplication(), "The app is blocked", Toast.LENGTH_LONG).show();
+ }
+ });
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to toast");
+ e.printStackTrace();
+ }
+ return ActivityManager.START_ABORTED;
+ }
+ // [SEI-lixj-2020-11-05] Skyway prohibits launching disable apps }
+
synchronized (mService.mGlobalLock) {
final ActivityStack stack = mRootActivityContainer.getTopDisplayFocusedStack();
stack.mConfigWillChange = globalConfig != null
三、期间遇到问题以及解决方法:
1.想要获取配置xml值,Toast提示,在frameworks Activity找不到实例Context ,解决方法,直接ActivityThread.currentApplication()获取.
2.Toast出错,原因是需要在主线程执行,解决方法,new Handler 获取一个MainLooper再执行Toast.如new Handler(Looper.getMainLooper()).post(() ->{Toast.makeText(ActivityThread.currentApplication(), “The app is blocked”, Toast.LENGTH_LONG).show();});
3.frameworks res添加xml配置,实践得需要在symbols.xml中先注册,否则编译时找不到资源值.
4.系统上库认证,极端情况可能出现Handler Looper.prepare()异常,主线程未进入循环队列。