Android Frameworks系统层禁止指定应用启动【disable startActivity】实例

背景:应系统任务要求,在android设备开机 和 列表更新时 读取系统属性disable的应用列表,禁止任何地方启动该应用的Activity,并Toast 3秒提示:The app is blocked.
通过overlay配置进行控制是否启用该功能,轻量级,不影响系统功能。

此次修改是针对Android Q(SDK 29),也就是android 10来说的

一、熟悉Activity启动流程

  1. 首先从 Context.startActivity() 开始,调到 ContextImpl.startActivityForResult()
  2. 通过 Instrumentation 通过 AIDL 调用 ActivityTaskManagerService(也是AMS的一部分)的 startActivity() 方法,从应用进程调用到系统进程
Activity Instrumentation ActivityTaskManagerService startActivity startActivityForResult startActivity startActivityAsUser Activity Instrumentation ActivityTaskManagerService
  1. SystemServer 会有一连串的调用和重载,主要是对于 Intent 的解析(发生在 ActivityStarter)、任务栈的管理(发生在 ActivityStack 和 ActivityStackSupervisor)、进程的创建和调度(发生在 AMS) 等等
ActivityTaskManagerService ActivityStarter RootActivityContainer ActivityStack startActivityAsUser startActivityAsUser startActivityMayWait startActivity startActivityUnchecked resumeFocusedStacksTopActivities ActivityTaskManagerService ActivityStarter RootActivityContainer ActivityStack

如果进程没有创建,会先走一遍进程创建的流程,并把这个启动 Activity 的任务悬挂起来,在进程创建好并报道之后回调生命周期

RootActivityContainer ActivityStack ActivityStackSupervisor ActivityManagerService.LocalService resumeFocusedStacksTopActivities resumeTopActivityUncheckedLocked resumeTopActivityInnerLocked startSpecificActivityLocked startProcess RootActivityContainer ActivityStack ActivityStackSupervisor ActivityManagerService.LocalService
  1. 在保证进程已经创建好之后,通过 IApplicationThread 进行跨进程通讯,从系统进程回到应用进程,通过 ActivityThread 中的 Handler 接收到消息并在主线程处理
  2. 最后又通过 Instrumentation 反射创建要启动的 Activity,为其创建 ContextImpl,执行 attach 进行一些初始化操作,并调用创建的 Activity 的 onCreate() 回调生命周期

也就是说启动 Activity 其实是至少经过了两次跨进程通讯(App -> SystemServer -> App)才将 Activity 启动起来的。

值得注意的是

  1. Android Q 以后把很多 ActivityManagerService 的逻辑移到了 ActivityTaskManagerService
  2. 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()异常,主线程未进入循环队列。

参考

Activity启动流程?

更多Android系统和应用开发的讨论,欢迎加入我

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值