Android8.0中实现APP禁用模式(二)

上一篇文章说了,打开一个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是打开的或者是最近应用视图正在显示。针对这两种情况,目前的改动都还无法阻止用户使用应用,如果破解这个难题呢?请听下回分解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值