Files
Flexera-Launcher/quickstep/src/com/android/quickstep/RecentTasksList.java
T
Andy Wickham b922dccbf5 Cleanup API checks for P, Q, and R.
Test: Build with gradle
Flag: NA
Change-Id: I3ef17090741d14bebd76cf09cf3dc5ac4f11e686
2024-01-24 14:58:08 -08:00

389 lines
16 KiB
Java

/*
* Copyright (C) 2014 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 com.android.quickstep;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.os.Process;
import android.os.RemoteException;
import android.util.SparseBooleanArray;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksList {
private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
private final KeyguardManager mKeyguardManager;
private final LooperExecutor mMainThreadExecutor;
private final SystemUiProxy mSysUiProxy;
// The list change id, increments as the task list changes in the system
private int mChangeId;
// Whether we are currently updating the tasks in the background (up to when the result is
// posted back on the main thread)
private boolean mLoadingTasksInBackground;
private TaskLoadResult mResultsBg = INVALID_RESULT;
private TaskLoadResult mResultsUi = INVALID_RESULT;
private RecentsModel.RunningTasksListener mRunningTasksListener;
// Tasks are stored in order of least recently launched to most recently launched.
private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
SystemUiProxy sysUiProxy) {
mMainThreadExecutor = mainThreadExecutor;
mKeyguardManager = keyguardManager;
mChangeId = 1;
mSysUiProxy = sysUiProxy;
sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
@Override
public void onRecentTasksChanged() throws RemoteException {
mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
}
@Override
public void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
mMainThreadExecutor.execute(() -> {
RecentTasksList.this.onRunningTaskAppeared(taskInfo);
});
}
@Override
public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
mMainThreadExecutor.execute(() -> {
RecentTasksList.this.onRunningTaskVanished(taskInfo);
});
}
});
// We may receive onRunningTaskAppeared events later for tasks which have already been
// included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
// onRunningTaskVanished for tasks not included in the returned list. These cases will be
// addressed when the tasks are added to/removed from mRunningTasks.
initRunningTasks(mSysUiProxy.getRunningTasks(Integer.MAX_VALUE));
}
@VisibleForTesting
public boolean isLoadingTasksInBackground() {
return mLoadingTasksInBackground;
}
/**
* Fetches the task keys skipping any local cache.
*/
public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) {
// Kick off task loading in the background
UI_HELPER_EXECUTOR.execute(() -> {
ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1,
true /* loadKeysOnly */);
mMainThreadExecutor.execute(() -> callback.accept(tasks));
});
}
/**
* Asynchronously fetches the list of recent tasks, reusing cached list if available.
*
* @param loadKeysOnly Whether to load other associated task data, or just the key
* @param callback The callback to receive the list of recent tasks
* @return The change id of the current task list
*/
public synchronized int getTasks(boolean loadKeysOnly,
Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
// so that requestID can be returned first.
if (callback != null) {
// Copy synchronously as the changeId might change by next frame
// and filter GroupTasks
ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
.map(GroupTask::copy)
.collect(Collectors.toCollection(ArrayList<GroupTask>::new));
mMainThreadExecutor.post(() -> {
callback.accept(result);
});
}
return requestLoadId;
}
// Kick off task loading in the background
mLoadingTasksInBackground = true;
UI_HELPER_EXECUTOR.execute(() -> {
if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
}
TaskLoadResult loadResult = mResultsBg;
mMainThreadExecutor.execute(() -> {
mLoadingTasksInBackground = false;
mResultsUi = loadResult;
if (callback != null) {
// filter the tasks if needed before passing them into the callback
ArrayList<GroupTask> result = mResultsUi.stream().filter(filter)
.map(GroupTask::copy)
.collect(Collectors.toCollection(ArrayList<GroupTask>::new));
callback.accept(result);
}
});
});
return requestLoadId;
}
/**
* @return Whether the provided {@param changeId} is the latest recent tasks list id.
*/
public synchronized boolean isTaskListValid(int changeId) {
return mChangeId == changeId;
}
public void onRecentTasksChanged() {
invalidateLoadedTasks();
}
private synchronized void invalidateLoadedTasks() {
UI_HELPER_EXECUTOR.execute(() -> mResultsBg = INVALID_RESULT);
mResultsUi = INVALID_RESULT;
mChangeId++;
}
/**
* Registers a listener for running tasks
*/
public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) {
mRunningTasksListener = listener;
}
/**
* Removes the previously registered running tasks listener
*/
public void unregisterRunningTasksListener() {
mRunningTasksListener = null;
}
private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) {
// Tasks are retrieved in order of most recently launched/used to least recently launched.
mRunningTasks = new ArrayList<>(runningTasks);
Collections.reverse(mRunningTasks);
}
/**
* Gets the set of running tasks.
*/
public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
return mRunningTasks;
}
private void onRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
// Make sure this task is not already in the list
for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
if (taskInfo.taskId == existingTask.taskId) {
return;
}
}
mRunningTasks.add(taskInfo);
if (mRunningTasksListener != null) {
mRunningTasksListener.onRunningTasksChanged();
}
}
private void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
// Find the task from the list of running tasks, if it exists
for (ActivityManager.RunningTaskInfo existingTask : mRunningTasks) {
if (existingTask.taskId != taskInfo.taskId) continue;
mRunningTasks.remove(existingTask);
if (mRunningTasksListener != null) {
mRunningTasksListener.onRunningTasksChanged();
}
return;
}
}
/**
* Loads and creates a list of all the recent tasks.
*/
@VisibleForTesting
TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier();
ArrayList<GroupedRecentTaskInfo> rawTasks =
mSysUiProxy.getRecentTasks(numTasks, currentUserId);
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(rawTasks);
SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
@Override
public boolean get(int key) {
if (indexOfKey(key) < 0) {
// Fill the cached locked state as we fetch
put(key, mKeyguardManager.isDeviceLocked(key));
}
return super.get(key);
}
};
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
if (isDesktopModeSupported() && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
allTasks.add(desktopTask);
continue;
}
ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
ActivityManager.RecentTaskInfo taskInfo2 = rawTask.getTaskInfo2();
Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
Task task1 = loadKeysOnly
? new Task(task1Key)
: Task.from(task1Key, taskInfo1,
tmpLockedUsers.get(task1Key.userId) /* isLocked */);
task1.setLastSnapshotData(taskInfo1);
Task task2 = null;
if (taskInfo2 != null) {
// Is split task
Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
task2 = loadKeysOnly
? new Task(task2Key)
: Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
task2.setLastSnapshotData(taskInfo2);
} else {
// Is fullscreen task
if (numVisibleTasks > 0) {
boolean isExcluded = (taskInfo1.baseIntent.getFlags()
& FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
if (taskInfo1.isTopActivityTransparent && isExcluded) {
// If there are already visible tasks, then ignore the excluded tasks and
// don't add them to the returned list
continue;
}
}
}
if (taskInfo1.isVisible) {
numVisibleTasks++;
}
final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
}
return allTasks;
}
private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
Task.TaskKey key = new Task.TaskKey(taskInfo);
Task task = Task.from(key, taskInfo, false);
task.setLastSnapshotData(taskInfo);
task.positionInParent = taskInfo.positionInParent;
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
tasks.add(task);
}
return new DesktopTask(tasks);
}
private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
ArrayList<GroupTask> newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
newTasks.add(tasks.get(i).copy());
}
return newTasks;
}
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "RecentTasksList:");
writer.println(prefix + " mChangeId=" + mChangeId);
writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
for (GroupTask task : mResultsUi) {
Task task1 = task.task1;
Task task2 = task.task2;
ComponentName cn1 = task1.getTopComponent();
ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
writer.println(prefix + " t1: (id=" + task1.key.id
+ "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
+ " t2: (id=" + (task2 != null ? task2.key.id : "-1")
+ "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
}
writer.println(prefix + " ]");
int currentUserId = Process.myUserHandle().getIdentifier();
ArrayList<GroupedRecentTaskInfo> rawTasks =
mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
writer.println(prefix + " rawTasks=[");
for (GroupedRecentTaskInfo task : rawTasks) {
TaskInfo taskInfo1 = task.getTaskInfo1();
TaskInfo taskInfo2 = task.getTaskInfo2();
ComponentName cn1 = taskInfo1.topActivity;
ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null;
writer.println(prefix + " t1: (id=" + taskInfo1.taskId
+ "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
+ " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1")
+ "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
}
writer.println(prefix + " ]");
}
@VisibleForTesting
static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
// If the result was loaded with keysOnly = true
final boolean mKeysOnly;
TaskLoadResult(int requestId, boolean keysOnly, int size) {
super(size);
mRequestId = requestId;
mKeysOnly = keysOnly;
}
boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
}
}
}