Call into shell for recent tasks

- No change in behavior from today until groups are actually returned
  from the shell

Bug: 202740477
Test: atest RecentTasksListTest
Change-Id: I4ac7b472ce2e0a3b2574dc6d8f4c1761a0ad993a
This commit is contained in:
Winson Chung
2021-10-16 22:49:07 -07:00
parent e0567d45a1
commit 487ccf4cd3
8 changed files with 179 additions and 119 deletions
@@ -22,35 +22,33 @@ import android.annotation.TargetApi;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.os.Build; import android.os.Build;
import android.os.Process; import android.os.Process;
import android.util.Log; import android.os.RemoteException;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperExecutor;
import com.android.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.KeyguardManagerCompat;
import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
* Manages the recent task list from the system, caching it as necessary. * Manages the recent task list from the system, caching it as necessary.
*/ */
@TargetApi(Build.VERSION_CODES.R) @TargetApi(Build.VERSION_CODES.R)
public class RecentTasksList extends TaskStackChangeListener { public class RecentTasksList {
private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0); private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
private final KeyguardManagerCompat mKeyguardManager; private final KeyguardManagerCompat mKeyguardManager;
private final LooperExecutor mMainThreadExecutor; private final LooperExecutor mMainThreadExecutor;
private final ActivityManagerWrapper mActivityManagerWrapper; private final SystemUiProxy mSysUiProxy;
// The list change id, increments as the task list changes in the system // The list change id, increments as the task list changes in the system
private int mChangeId; private int mChangeId;
@@ -62,12 +60,17 @@ public class RecentTasksList extends TaskStackChangeListener {
private TaskLoadResult mResultsUi = INVALID_RESULT; private TaskLoadResult mResultsUi = INVALID_RESULT;
public RecentTasksList(LooperExecutor mainThreadExecutor, public RecentTasksList(LooperExecutor mainThreadExecutor,
KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) { KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
mMainThreadExecutor = mainThreadExecutor; mMainThreadExecutor = mainThreadExecutor;
mKeyguardManager = keyguardManager; mKeyguardManager = keyguardManager;
mChangeId = 1; mChangeId = 1;
mActivityManagerWrapper = activityManagerWrapper; mSysUiProxy = sysUiProxy;
TaskStackChangeListeners.getInstance().registerTaskStackListener(this); sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
@Override
public void onRecentTasksChanged() throws RemoteException {
mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
}
});
} }
@VisibleForTesting @VisibleForTesting
@@ -78,10 +81,11 @@ public class RecentTasksList extends TaskStackChangeListener {
/** /**
* Fetches the task keys skipping any local cache. * Fetches the task keys skipping any local cache.
*/ */
public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) {
// Kick off task loading in the background // Kick off task loading in the background
UI_HELPER_EXECUTOR.execute(() -> { UI_HELPER_EXECUTOR.execute(() -> {
ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */); ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1,
true /* loadKeysOnly */);
mMainThreadExecutor.execute(() -> callback.accept(tasks)); mMainThreadExecutor.execute(() -> callback.accept(tasks));
}); });
} }
@@ -93,14 +97,15 @@ public class RecentTasksList extends TaskStackChangeListener {
* @param callback The callback to receive the list of recent tasks * @param callback The callback to receive the list of recent tasks
* @return The change id of the current task list * @return The change id of the current task list
*/ */
public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) { public synchronized int getTasks(boolean loadKeysOnly,
Consumer<ArrayList<GroupTask>> callback) {
final int requestLoadId = mChangeId; final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) { if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame, // The list is up to date, send the callback on the next frame,
// so that requestID can be returned first. // so that requestID can be returned first.
if (callback != null) { if (callback != null) {
// Copy synchronously as the changeId might change by next frame // Copy synchronously as the changeId might change by next frame
ArrayList<Task> result = copyOf(mResultsUi); ArrayList<GroupTask> result = copyOf(mResultsUi);
mMainThreadExecutor.post(() -> { mMainThreadExecutor.post(() -> {
callback.accept(result); callback.accept(result);
}); });
@@ -120,7 +125,7 @@ public class RecentTasksList extends TaskStackChangeListener {
mLoadingTasksInBackground = false; mLoadingTasksInBackground = false;
mResultsUi = loadResult; mResultsUi = loadResult;
if (callback != null) { if (callback != null) {
ArrayList<Task> result = copyOf(mResultsUi); ArrayList<GroupTask> result = copyOf(mResultsUi);
callback.accept(result); callback.accept(result);
} }
}); });
@@ -136,35 +141,7 @@ public class RecentTasksList extends TaskStackChangeListener {
return mChangeId == changeId; return mChangeId == changeId;
} }
@Override public void onRecentTasksChanged() {
public void onTaskStackChanged() {
invalidateLoadedTasks();
}
@Override
public void onRecentTaskListUpdated() {
// In some cases immediately after booting, the tasks in the system recent task list may be
// loaded, but not in the active task hierarchy in the system. These tasks are displayed in
// overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
// callback (those are for changes to the active tasks), but the task list is still updated,
// so we should also invalidate the change id to ensure we load a new list instead of
// reusing a stale list.
invalidateLoadedTasks();
}
@Override
public void onTaskRemoved(int taskId) {
invalidateLoadedTasks();
}
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
invalidateLoadedTasks();
}
@Override
public synchronized void onActivityUnpinned() {
invalidateLoadedTasks(); invalidateLoadedTasks();
} }
@@ -180,8 +157,8 @@ public class RecentTasksList extends TaskStackChangeListener {
@VisibleForTesting @VisibleForTesting
TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier(); int currentUserId = Process.myUserHandle().getIdentifier();
List<ActivityManager.RecentTaskInfo> rawTasks = ArrayList<GroupedRecentTaskInfo> rawTasks =
mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId); mSysUiProxy.getRecentTasks(numTasks, currentUserId);
// The raw tasks are given in most-recent to least-recent order, we need to reverse it // The raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(rawTasks); Collections.reverse(rawTasks);
@@ -197,45 +174,53 @@ public class RecentTasksList extends TaskStackChangeListener {
}; };
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size()); TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
for (ActivityManager.RecentTaskInfo rawTask : rawTasks) { for (GroupedRecentTaskInfo rawTask : rawTasks) {
Task.TaskKey taskKey = new Task.TaskKey(rawTask); ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1;
Task task; ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2;
if (!loadKeysOnly) { Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
boolean isLocked = tmpLockedUsers.get(taskKey.userId); Task task1 = loadKeysOnly
task = Task.from(taskKey, rawTask, isLocked); ? new Task(task1Key)
} else { : Task.from(task1Key, taskInfo1,
task = new Task(taskKey); tmpLockedUsers.get(task1Key.userId) /* isLocked */);
task1.setLastSnapshotData(taskInfo1);
Task task2 = null;
if (taskInfo2 != null) {
Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
task2 = loadKeysOnly
? new Task(task2Key)
: Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
task2.setLastSnapshotData(taskInfo2);
} }
task.setLastSnapshotData(rawTask); allTasks.add(new GroupTask(task1, task2));
allTasks.add(task);
} }
return allTasks; return allTasks;
} }
private ArrayList<Task> copyOf(ArrayList<Task> tasks) { private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
ArrayList<Task> newTasks = new ArrayList<>(); ArrayList<GroupTask> newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) {
newTasks.add(new Task(tasks.get(i))); newTasks.add(new GroupTask(tasks.get(i)));
} }
return newTasks; return newTasks;
} }
private static class TaskLoadResult extends ArrayList<Task> { private static class TaskLoadResult extends ArrayList<GroupTask> {
final int mId; final int mRequestId;
// If the result was loaded with keysOnly = true // If the result was loaded with keysOnly = true
final boolean mKeysOnly; final boolean mKeysOnly;
TaskLoadResult(int id, boolean keysOnly, int size) { TaskLoadResult(int requestId, boolean keysOnly, int size) {
super(size); super(size);
mId = id; mRequestId = requestId;
mKeysOnly = keysOnly; mKeysOnly = keysOnly;
} }
boolean isValidForRequest(int requestId, boolean loadKeysOnly) { boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
return mId == requestId && (!mKeysOnly || loadKeysOnly); return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
} }
} }
} }
@@ -34,6 +34,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.IconProvider.IconChangeListener; import com.android.launcher3.icons.IconProvider.IconChangeListener;
import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,7 +71,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
private RecentsModel(Context context) { private RecentsModel(Context context) {
mContext = context; mContext = context;
mTaskList = new RecentTasksList(MAIN_EXECUTOR, mTaskList = new RecentTasksList(MAIN_EXECUTOR,
new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));
IconProvider iconProvider = new IconProvider(context); IconProvider iconProvider = new IconProvider(context);
mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider); mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
@@ -95,7 +96,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
* always called on the UI thread. * always called on the UI thread.
* @return the request id associated with this call. * @return the request id associated with this call.
*/ */
public int getTasks(Consumer<ArrayList<Task>> callback) { public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback); return mTaskList.getTasks(false /* loadKeysOnly */, callback);
} }
@@ -120,9 +121,9 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
* @param callback Receives true if task is removed, false otherwise * @param callback Receives true if task is removed, false otherwise
*/ */
public void isTaskRemoved(int taskId, Consumer<Boolean> callback) { public void isTaskRemoved(int taskId, Consumer<Boolean> callback) {
mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> { mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
for (Task task : tasks) { for (GroupTask group : taskGroups) {
if (task.key.id == taskId) { if (group.containsTask(taskId)) {
callback.accept(false); callback.accept(false);
return; return;
} }
@@ -148,14 +149,15 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
ActivityManager.RunningTaskInfo runningTask = ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask(); ActivityManagerWrapper.getInstance().getRunningTask();
int runningTaskId = runningTask != null ? runningTask.id : -1; int runningTaskId = runningTask != null ? runningTask.id : -1;
mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> { mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
for (Task task : tasks) { for (GroupTask group : taskGroups) {
if (task.key.id == runningTaskId) { if (group.containsTask(runningTaskId)) {
// Skip the running task, it's not going to have an up-to-date snapshot by the // Skip the running task, it's not going to have an up-to-date snapshot by the
// time the user next enters overview // time the user next enters overview
continue; continue;
} }
mThumbnailCache.updateThumbnailInCache(task); mThumbnailCache.updateThumbnailInCache(group.task1);
mThumbnailCache.updateThumbnailInCache(group.task2);
} }
}); });
} }
@@ -15,6 +15,8 @@
*/ */
package com.android.quickstep; package com.android.quickstep;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -47,6 +49,9 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip; import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.splitscreen.ISplitScreen; import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener; import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.startingsurface.IStartingWindow; import com.android.wm.shell.startingsurface.IStartingWindow;
@@ -54,6 +59,7 @@ import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions; import com.android.wm.shell.transition.IShellTransitions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
/** /**
* Holds the reference to SystemUI. * Holds the reference to SystemUI.
@@ -72,6 +78,7 @@ public class SystemUiProxy implements ISystemUiProxy,
private IOneHanded mOneHanded; private IOneHanded mOneHanded;
private IShellTransitions mShellTransitions; private IShellTransitions mShellTransitions;
private IStartingWindow mStartingWindow; private IStartingWindow mStartingWindow;
private IRecentTasks mRecentTasks;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> { private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
MAIN_EXECUTOR.execute(() -> clearProxy()); MAIN_EXECUTOR.execute(() -> clearProxy());
}; };
@@ -82,6 +89,7 @@ public class SystemUiProxy implements ISystemUiProxy,
private ISplitScreenListener mPendingSplitScreenListener; private ISplitScreenListener mPendingSplitScreenListener;
private IStartingWindowListener mPendingStartingWindowListener; private IStartingWindowListener mPendingStartingWindowListener;
private ISmartspaceCallback mPendingSmartspaceCallback; private ISmartspaceCallback mPendingSmartspaceCallback;
private IRecentTasksListener mPendingRecentTasksListener;
private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>(); private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI // Used to dedupe calls to SystemUI
@@ -135,7 +143,7 @@ public class SystemUiProxy implements ISystemUiProxy,
public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen, public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
IOneHanded oneHanded, IShellTransitions shellTransitions, IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IStartingWindow startingWindow, IRecentTasks recentTasks,
ISmartspaceTransitionController smartSpaceTransitionController) { ISmartspaceTransitionController smartSpaceTransitionController) {
unlinkToDeath(); unlinkToDeath();
mSystemUiProxy = proxy; mSystemUiProxy = proxy;
@@ -145,6 +153,7 @@ public class SystemUiProxy implements ISystemUiProxy,
mShellTransitions = shellTransitions; mShellTransitions = shellTransitions;
mStartingWindow = startingWindow; mStartingWindow = startingWindow;
mSmartspaceTransitionController = smartSpaceTransitionController; mSmartspaceTransitionController = smartSpaceTransitionController;
mRecentTasks = recentTasks;
linkToDeath(); linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet. // re-attach the listeners once missing due to setProxy has not been initialized yet.
if (mPendingPipAnimationListener != null && mPip != null) { if (mPendingPipAnimationListener != null && mPip != null) {
@@ -167,6 +176,10 @@ public class SystemUiProxy implements ISystemUiProxy,
registerRemoteTransition(mPendingRemoteTransitions.get(i)); registerRemoteTransition(mPendingRemoteTransitions.get(i));
} }
mPendingRemoteTransitions.clear(); mPendingRemoteTransitions.clear();
if (mPendingRecentTasksListener != null && mRecentTasks != null) {
registerRecentTasksListener(mPendingRecentTasksListener);
mPendingRecentTasksListener = null;
}
if (mPendingSetNavButtonAlpha != null) { if (mPendingSetNavButtonAlpha != null) {
mPendingSetNavButtonAlpha.run(); mPendingSetNavButtonAlpha.run();
@@ -175,7 +188,7 @@ public class SystemUiProxy implements ISystemUiProxy,
} }
public void clearProxy() { public void clearProxy() {
setProxy(null, null, null, null, null, null, null); setProxy(null, null, null, null, null, null, null, null);
} }
// TODO(141886704): Find a way to remove this // TODO(141886704): Find a way to remove this
@@ -745,7 +758,6 @@ public class SystemUiProxy implements ISystemUiProxy,
} }
} }
// //
// SmartSpace transitions // SmartSpace transitions
// //
@@ -761,4 +773,43 @@ public class SystemUiProxy implements ISystemUiProxy,
mPendingSmartspaceCallback = callback; mPendingSmartspaceCallback = callback;
} }
} }
//
// Recents
//
public void registerRecentTasksListener(IRecentTasksListener listener) {
if (mRecentTasks != null) {
try {
mRecentTasks.registerRecentTasksListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRecentTasksListener", e);
}
} else {
mPendingRecentTasksListener = listener;
}
}
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
if (mRecentTasks != null) {
try {
mRecentTasks.unregisterRecentTasksListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call unregisterRecentTasksListener");
}
}
mPendingRecentTasksListener = null;
}
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
if (mRecentTasks != null) {
try {
return new ArrayList<>(Arrays.asList(mRecentTasks.getRecentTasks(numTasks,
RECENT_IGNORE_UNAVAILABLE, userId)));
} catch (RemoteException e) {
Log.w(TAG, "Failed call getRecentTasks", e);
}
}
return new ArrayList<>();
}
} }
@@ -104,6 +104,9 @@ public class TaskThumbnailCache {
* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache. * Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
*/ */
public void updateThumbnailInCache(Task task) { public void updateThumbnailInCache(Task task) {
if (task == null) {
return;
}
Preconditions.assertUIThread(); Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache // Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) { if (task.thumbnail == null) {
@@ -25,6 +25,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE; import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -107,6 +108,7 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip; import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.splitscreen.ISplitScreen; import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.startingsurface.IStartingWindow; import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.transition.IShellTransitions; import com.android.wm.shell.transition.IShellTransitions;
@@ -162,9 +164,11 @@ public class TouchInteractionService extends Service
ISmartspaceTransitionController smartspaceTransitionController = ISmartspaceTransitionController smartspaceTransitionController =
ISmartspaceTransitionController.Stub.asInterface( ISmartspaceTransitionController.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER)); bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER));
IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
MAIN_EXECUTOR.execute(() -> { MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip, SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow, splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
smartspaceTransitionController); smartspaceTransitionController);
TouchInteractionService.this.initInputMonitor(); TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */); preloadOverview(true /* fromInit */);
@@ -42,6 +42,7 @@ import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -153,29 +154,32 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
} }
@Override @Override
protected void applyLoadPlan(ArrayList<Task> tasks) { protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
// When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app. // track the index of the next task appropriately, as if we are switching on any other app.
// TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
int runningTaskId = getTaskIdsForRunningTaskView()[0]; int runningTaskId = getTaskIdsForRunningTaskView()[0];
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId && !tasks.isEmpty()) { if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
&& !taskGroups.isEmpty()) {
// Check if the task list has running task // Check if the task list has running task
boolean found = false; boolean found = false;
for (Task t : tasks) { for (GroupTask group : taskGroups) {
if (t.key.id == runningTaskId) { if (group.containsTask(runningTaskId)) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1); ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
newList.addAll(tasks); newList.addAll(taskGroups);
newList.add(Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false)); newList.add(new GroupTask(
tasks = newList; Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false),
null));
taskGroups = newList;
} }
} }
super.applyLoadPlan(tasks); super.applyLoadPlan(taskGroups);
} }
@Override @Override
@@ -167,6 +167,7 @@ import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.VibratorWrapper; import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -1296,13 +1297,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
updateGridProperties(); updateGridProperties();
} }
protected void applyLoadPlan(ArrayList<Task> tasks) { protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
if (mPendingAnimation != null) { if (mPendingAnimation != null) {
mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks)); mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
return; return;
} }
if (tasks == null || tasks.isEmpty()) { if (taskGroups == null || taskGroups.isEmpty()) {
removeTasksViewsAndClearAllButton(); removeTasksViewsAndClearAllButton();
onTaskStackUpdated(); onTaskStackUpdated();
return; return;
@@ -1324,10 +1325,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
LauncherSplitScreenListener.INSTANCE.getNoCreate().getPersistentSplitIds(); LauncherSplitScreenListener.INSTANCE.getNoCreate().getPersistentSplitIds();
int requiredGroupTaskViews = splitTaskIds.length / 2; int requiredGroupTaskViews = splitTaskIds.length / 2;
// TODO(b/202740477): Update once grouped tasks are returned
// Subtract half the number of split tasks and not total number because we've already // Subtract half the number of split tasks and not total number because we've already
// added a GroupedTaskView when swipe up gesture happens. // added a GroupedTaskView when swipe up gesture happens.
// This will need to change if we start showing GroupedTaskViews during swipe up from home // This will need to change if we start showing GroupedTaskViews during swipe up from home
int requiredTaskViewCount = tasks.size() - requiredGroupTaskViews; int requiredTaskViewCount = taskGroups.size() - requiredGroupTaskViews;
if (getTaskViewCount() != requiredTaskViewCount) { if (getTaskViewCount() != requiredTaskViewCount) {
if (indexOfChild(mClearAllButton) != -1) { if (indexOfChild(mClearAllButton) != -1) {
@@ -1360,11 +1362,12 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
+ " runningTaskViewId: " + mRunningTaskViewId + " runningTaskViewId: " + mRunningTaskViewId
+ " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId)); + " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId));
for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = tasks.size() - 1; for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = taskGroups.size() - 1;
taskViewIndex >= 0; taskViewIndex >= 0;
taskViewIndex--, taskDataIndex--) { taskViewIndex--, taskDataIndex--) {
final int pageIndex = requiredTaskViewCount - taskViewIndex - 1; final int pageIndex = requiredTaskViewCount - taskViewIndex - 1;
final Task task = tasks.get(taskDataIndex); // TODO(b/202740477): Temporary assumption, to be updated once groups are actually used
final Task task = taskGroups.get(taskDataIndex).task1;
final TaskView taskView = (TaskView) getChildAt(pageIndex); final TaskView taskView = (TaskView) getChildAt(pageIndex);
if (taskView instanceof GroupedTaskView) { if (taskView instanceof GroupedTaskView) {
Task leftTop; Task leftTop;
@@ -1372,11 +1375,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
if (task.key.id == splitTaskIds[0]) { if (task.key.id == splitTaskIds[0]) {
leftTop = task; leftTop = task;
taskDataIndex--; taskDataIndex--;
rightBottom = tasks.get(taskDataIndex); rightBottom = taskGroups.get(taskDataIndex).task1;
} else { } else {
rightBottom = task; rightBottom = task;
taskDataIndex--; taskDataIndex--;
leftTop = tasks.get(taskDataIndex); leftTop = taskGroups.get(taskDataIndex).task1;
} }
((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState, ((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState,
mSplitBoundsConfig); mSplitBoundsConfig);
@@ -30,70 +30,78 @@ import android.app.ActivityManager;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperExecutor;
import com.android.systemui.shared.recents.model.GroupTask;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.KeyguardManagerCompat;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@SmallTest @SmallTest
public class RecentTasksListTest { public class RecentTasksListTest {
private ActivityManagerWrapper mockActivityManagerWrapper; @Mock
private SystemUiProxy mockSystemUiProxy;
// Class under test // Class under test
private RecentTasksList mRecentTasksList; private RecentTasksList mRecentTasksList;
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this);
LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class); KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
mockActivityManagerWrapper = mock(ActivityManagerWrapper.class);
mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat, mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
mockActivityManagerWrapper); mockSystemUiProxy);
} }
@Test @Test
public void onTaskRemoved_doesNotFetchTasks() { public void onRecentTasksChanged_doesNotFetchTasks() {
mRecentTasksList.onTaskRemoved(0); mRecentTasksList.onRecentTasksChanged();
verify(mockActivityManagerWrapper, times(0)) verify(mockSystemUiProxy, times(0))
.getRecentTasks(anyInt(), anyInt());
}
@Test
public void onTaskStackChanged_doesNotFetchTasks() {
mRecentTasksList.onTaskStackChanged();
verify(mockActivityManagerWrapper, times(0))
.getRecentTasks(anyInt(), anyInt()); .getRecentTasks(anyInt(), anyInt());
} }
@Test @Test
public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo());
.thenReturn(Collections.singletonList(recentTaskInfo)); when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, true); List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
true);
assertEquals(1, taskList.size()); assertEquals(1, taskList.size());
assertNull(taskList.get(0).taskDescription.getLabel()); assertNull(taskList.get(0).task1.taskDescription.getLabel());
assertNull(taskList.get(0).task2.taskDescription.getLabel());
} }
@Test @Test
public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() { public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() {
String taskDescription = "Wheeee!"; String taskDescription = "Wheeee!";
ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo();
recentTaskInfo.taskDescription = new ActivityManager.TaskDescription(taskDescription); task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo();
.thenReturn(Collections.singletonList(recentTaskInfo)); task2.taskDescription = new ActivityManager.TaskDescription();
GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
task1, task2);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1, false); List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
false);
assertEquals(1, taskList.size()); assertEquals(1, taskList.size());
assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel()); assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel());
assertNull(taskList.get(0).task2.taskDescription.getLabel());
} }
} }