背景
前段时间遇到了一个比较有意思的问题点:Android 10 ~ 12无法创建携带FLAG_TRUSTED标识virtualdisplay,也就是说我们没有权限直接将SecondaryActivity启动到virtualdisplay上,这个时候只能App自己想办法解决了。
解题思路
样板间App是通过MainActivity启动SecondaryActivity的,如果能监听到MainActivity启动,Android副屏(模拟器)App通过某种方式启动SecondaryActivity即可。
思路一
想要监听第三方App是否启动最直接的办法就是ps,哪想要监听第三方App中acitivity是否启动最直接的办法就是am stack list | grep MainActivity,那么我们启动一个线程,每间隔2s就检测一下MainActivity是否已启动,如果没有启动,我们就等待下次检测,如果已经启动,我们就通过am start -n SecondaryActivity --display displayId命令将SecondaryActivity启动起来。
伪代码大致如下:
private static boolean activityRunning(String activity) {
String cmd = "am stack list | grep " + activity;
Shell.Result sr = Shell.execCommand(cmd);
return sr.mResult == 0;
}
private static void startActivity(String activity, int displayId) {
StringBuilder sb = new StringBuilder();
sb.append("am start -n ");
sb.append(activity);
sb.append(" --display ");
sb.append(displayId);
String text = sb.toString();
Shell.Result sr = Shell.execCommand(text);
if (sr.mResult == 0) {
System.out.println("am start secondActivity success");
}
}
private boolean isReady() {
return activityRunning(MainActivity) && !activityRunning(SecondActivity);
}
class DetectorThread extends Thread {
DetectorThread() {
super("DetectorThread");
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
if (isReady()) {
Thread.sleep(2000);
if (isReady()) {
startActivity(mSecondActivity, mDisplayId);
}
}
Thread.sleep(2000);
} catch (Exception e) {
System.out.println("detector exception:" + e);
}
}
}
}
上述代码简单易懂,经过测试非常OK。
上述代码虽然能达到目的,但是并不优雅,第一需要借助ADB shell,am stack list会很耗时,第二需要启动一个线程,且此线程需要一直跑着,哪Android有没有专门监听acitivy启动的API呢?
思路二
要想找到监听activity的API,首先想到的是android.app.AcivityManager或com.android.server.am.ActivityManagerService。果然在ActivityManagerService中找到了IActivityController
@Override
public void setActivityController(IActivityController controller, boolean imAMonkey) {
if (controller != null) {
Binder.allowBlocking(controller.asBinder());
}
mActivityTaskManager.setActivityController(controller, imAMonkey);
}
/*
**
** Copyright 2009, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.app;
import android.content.Intent;
/**
* Testing interface to monitor what is happening in the activity manager
* while tests are running. Not for normal application development.
* {@hide}
*/
interface IActivityController
{
/**
* The system is trying to start an activity. Return true to allow
* it to be started as normal, or false to cancel/reject this activity.
*/
boolean activityStarting(in Intent intent, String pkg);
/**
* The system is trying to return to an activity. Return true to allow
* it to be resumed as normal, or false to cancel/reject this activity.
*/
boolean activityResuming(String pkg);
/**
* An application process has crashed (in Java). Return true for the
* normal error recovery (app crash dialog) to occur, false to kill
* it immediately.
*/
boolean appCrashed(String processName, int pid,
String shortMsg, String longMsg,
long timeMillis, String stackTrace);
/**
* Early call as soon as an ANR is detected.
*/
int appEarlyNotResponding(String processName, int pid, String annotation);
/**
* An application process is not responding. Return 0 to show the "app
* not responding" dialog, 1 to continue waiting, or -1 to kill it
* immediately.
*/
int appNotResponding(String processName, int pid, String processStats);
/**
* The system process watchdog has detected that the system seems to be
* hung. Return 1 to continue waiting, or -1 to let it continue with its
* normal kill.
*/
int systemNotResponding(String msg);
}
IActivityController.activityStarting就能监听到任何activity的启动。
Android副屏(模拟器)App项目按照Android原生路径android.app引入IActivityController.aidl进行编译,以欺骗sdk。
ActivityManagerService可通过android.app.ActivityManagerNative或者android.app.IActivityManager获得,然后可直接调用setActivityController接口设置自己监听器。
public void setActivityController(IActivityController.Stub stub) {
try {
Class<?> cls = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = cls.getDeclaredMethod("getDefault");
IInterface am = (IInterface) getDefaultMethod.invoke(null);
Method method = am.getClass().getMethod("setActivityController", IActivityController.class, boolean.class);
method.invoke(mManager, stub, true);
} catch (Exception e) {
e.printStackTrace();
}
}
编译通过后,进行测试,非常OK。
然后可以通过原生startActivityAsUser API替代am start,此处不在赘述。
彩蛋
IActivityController.activityStarting的注释:The system is trying to start an activity. Return true to allowt to be started as normal, or false to cancel/reject this activity.
返回true允许activity启动,返回false就是取消或者拒绝activity启动。
那么我们只要有ADB权限就可以有针对的禁止任意APP的任意activity启动,比如:游戏、支付、直播等等,这不就是专治不听话熊孩子的神器吗,程序员爸爸/妈妈快点行动起来吧,开发一个专治熊孩的APP。