上一篇文章说了,打开一个APP的方式有三种:1、从启动器点击图标启动;2、点击APP弹出的通知启动;3、点击多任务键,选择APP。现在就来实现在多任务菜单中实现APP的禁用。
入口
Framework中对各种按键的处理都是在PhoneWIndowManager中,这里会通过几个intercept函数,对Home、Back、媒体按键等进行拦截,不让这几个按键的键值传递给APP。我们在PhoneWindowManager.java中搜索KeyEvent.KEYCODE_APP_SWITCH:
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
...
} else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
if (!keyguardOn) {
if (down && repeatCount == 0) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
return -1;
}
...
}
通过这两个函数名,我们可以看出来,当第一次按下按键的时候,就会载入最近的APP,然后,当按键放开的时候,就显示最近的APP。那么,对我们来说,只要在载入最近的APP的时候,过滤掉被禁用的APP即可。
跟踪preloadRecentsApps函数的代码,最终一路跟踪到了SystemUI的Recents中:
@Override
public void preloadRecentApps() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents(); // 关键点在这里
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.preloadRecents();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
}
在这个函数中,如果当前的用户是System用户,那么就调用RecentsImpl的preloadRecents函数。
public void preloadRecents() {
// Skip preloading if the task is locked
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isScreenPinningActive()) {
return;
}
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity) only if there is a task to animate to. Post this to ensure that we
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
MutableBoolean isHomeStackVisible = new MutableBoolean(true);
if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
if (runningTask == null) {
return;
}
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
// Timothy:看这里
loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken
// for the pausing activity)
preloadIcon(runningTask.id);
// At this point, we don't know anything about the stack state. So only
// calculate the dimensions of the thumbnail that we need for the transition
// into Recents, but do not draw it until we construct the activity options when
// we start Recents
updateHeaderBarLayout(stack, null /* window rect override*/);
}
}
});
}
这个函数里面,用异步的方法,调用了RecentsTaskLoader的preLoadTasks方法:
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
boolean includeFrontMostExcludedTask) {
try {
Trace.beginSection("preloadPlan");
// 看这里
plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
} finally {
Trace.endSection();
}
}
这里又调用到了TaskloadTasks的preloadPlan函数:
void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
boolean includeFrontMostExcludedTask) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
// Timothy:看这里,关键就在这。
if (mRawTasks == null) {
preloadRawTasks(includeFrontMostExcludedTask);
}
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
SparseIntArray affiliatedTaskCounts = new SparseIntArray();
SparseBooleanArray lockedUsers = new SparseBooleanArray();
String dismissDescFormat = mContext.getString(
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
int currentUserId = mPreloadedUserId;
long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
if (RecentsDebugFlags.Static.EnableMockTasks) {
lastStackActiveTime = 0;
}
long newLastStackActiveTime = -1;
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
// This task is only shown in the stack if it satisfies the historical time or min
// number of tasks constraints. Freeform tasks are also always shown.
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
boolean isStackTask;
if (Recents.getConfiguration().isGridEnabled) {
// When grid layout is enabled, we only show the first
// TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks.
isStackTask = t.lastActiveTime >= lastStackActiveTime &&
i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else if (Recents.getConfiguration().isLowRamDevice) {
// Show a max of 3 items
isStackTask = t.lastActiveTime >= lastStackActiveTime &&
i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
} else {
isStackTask = isFreeformTask || !isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
}
boolean isLaunchTarget = taskKey.id == runningTaskId;
// The last stack active time is the baseline for which we show visible tasks. Since
// the system will store all the tasks, we don't want to show the tasks prior to the
// last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
// the other stack-task constraints.
if (isStackTask && newLastStackActiveTime < 0) {
newLastStackActiveTime = t.lastActiveTime;
}
// Load the title, icon, and color
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
String titleDescription = loader.getAndUpdateContentDescription(taskKey,
t.taskDescription, res);
String dismissDescription = String.format(dismissDescFormat, titleDescription);
String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
false /* loadIfNotCached */, false /* storeInCache */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
if (lockedUsers.indexOfKey(t.userId) < 0) {
lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
}
boolean isLocked = lockedUsers.get(t.userId);
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
isLocked);
allTasks.add(task);
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
affiliatedTasks.put(taskKey.id, taskKey);
}
if (newLastStackActiveTime != -1) {
Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
newLastStackActiveTime, currentUserId);
}
// Initialize the stacks
mStack = new TaskStack();
mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
}
这个函数又很长,但是仔细看一下这个函数的开头:
if (mRawTasks == null) {
preloadRawTasks(includeFrontMostExcludedTask);
}
如果mRawTasks这个任务栈为空,就通过preloadRawTasks函数重新载入任务栈。
void preloadRawTasks(boolean includeFrontMostExcludedTask) {
SystemServicesProxy ssp = Recents.getSystemServices();
int currentUserId = ssp.getCurrentUser();
updateCurrentQuietProfilesCache(currentUserId);
mPreloadedUserId = currentUserId;
// Timothy:看这里
mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
}
这里又调用了SystemServiceProxy的getRecentTasks方法。SystemServiceProxy从其名字就可以看出,是个系统代理,它封装了从系统获取各种信息的接口。离真相越来越近了。我们看一下getRecentTasks函数。
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
if (mAm == null) return null;
// If we are mocking, then create some recent tasks
if (RecentsDebugFlags.Static.EnableMockTasks) {
ArrayList<ActivityManager.RecentTaskInfo> tasks =
new ArrayList<ActivityManager.RecentTaskInfo>();
int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
for (int i = 0; i < count; i++) {
// Create a dummy component name
int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
ComponentName cn = new ComponentName("com.android.test" + packageIndex,
"com.android.test" + i + ".Activity");
String description = "" + i + " - " +
Long.toString(Math.abs(new Random().nextLong()), 36);
// Create the recent task info
ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
rti.id = rti.persistentId = rti.affiliatedTaskId = i;
rti.baseIntent = new Intent();
rti.baseIntent.setComponent(cn);
rti.description = description;
rti.firstActiveTime = rti.lastActiveTime = i;
if (i % 2 == 0) {
rti.taskDescription = new ActivityManager.TaskDescription(description,
Bitmap.createBitmap(mDummyIcon), null,
0xFF000000 | (0xFFFFFF & new Random().nextInt()),
0xFF000000 | (0xFFFFFF & new Random().nextInt()),
0, 0);
} else {
rti.taskDescription = new ActivityManager.TaskDescription();
}
tasks.add(rti);
}
return tasks;
}
// Remove home/recents/excluded tasks
int minNumTasksToQuery = 10;
int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
ActivityManager.RECENT_INCLUDE_PROFILES;
if (includeFrontMostExcludedTask) {
flags |= ActivityManager.RECENT_WITH_EXCLUDED;
}
List<ActivityManager.RecentTaskInfo> tasks = null;
try {
tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
} catch (Exception e) {
Log.e(TAG, "Failed to get recent tasks", e);
}
// Break early if we can't get a valid set of tasks
if (tasks == null) {
return new ArrayList<>();
}
boolean isFirstValidTask = true;
Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
while (iter.hasNext()) {
ActivityManager.RecentTaskInfo t = iter.next();
// NOTE: The order of these checks happens in the expected order of the traversal of the
// tasks
// Remove the task if it or it's package are blacklsited
if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
iter.remove();
continue;
}
// Remove the task if it is marked as excluded, unless it is the first most task and we
// are requested to include it
boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
== Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
isExcluded |= quietProfileIds.contains(t.userId);
if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
iter.remove();
}
isFirstValidTask = false;
}
return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
}
这个函数很长,但是第一个if前有个注释:
If we are mocking, then create some recent tasks
这个意思是,如果当前是在mocking,那么就创建一些tasks。虽然我不知道mocking是什么意思,但是,最近的任务是要从系统获取的,创建的肯定就是一些假的测试数据,所以,这一大段代码可以直接跳过了。
接下来就调用了ActivityManager的getRecentTasksForUser方法获取最近的任务列表。从这里就进入了Framework了。
因为我只是想隐藏被禁用的任务,并不想从系统的任务栈中真的去掉任务,这样,在禁用解除之后,被禁用的应用能再次显示,所以,就不去Framework中追踪代码了。
在调用了getRecentTasksForUser方法之后,就是使用Iterator来遍历列表。
看到这个Iterator,我眼前一亮。为啥要用Iterator呢?如果只是简单的遍历,直接用for循环就行了,除非想在遍历的时候对列表进行操作,那么我们是不是也可以在遍历的时候加上我们的过滤操作呢?
看这个遍历体里的两段注释:
// Remove the task if it or it’s package are blacklsited
// Remove the task if it is marked as excluded, unless it is the first most task and we
// are requested to include it
这两段的注释就是说,要从任务列表里面去掉黑名单的包和标记为excluded的包。
所以,我们的猜测是正确的,只需要在这里获取被禁用的APP包名,然后从最近任务列表中去掉这些应用就好了。而且这里能获取到包名让我们进行过滤,完美!
到这里,需求就基本完成了。但是还有两种特殊情况,就是如果禁用app的时刻,APP是打开的或者是最近应用视图正在显示。