diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 3ba1a34bd432b..73c2e8bfa78f7 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -38,6 +38,7 @@ filegroup { path: "src", } +// Sources that have no dependencies that can be used directly downstream of this library filegroup { name: "wm_shell_util-sources", srcs: [ @@ -46,6 +47,7 @@ filegroup { path: "src", } +// Aidls which can be used directly downstream of this library filegroup { name: "wm_shell-aidls", srcs: [ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index e87b150a0709b..358553d705016 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -24,6 +24,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; @@ -43,6 +44,7 @@ public final class ShellCommandHandlerImpl { private final Optional mOneHandedOptional; private final Optional mHideDisplayCutout; private final Optional mAppPairsOptional; + private final Optional mRecentTasks; private final ShellTaskOrganizer mShellTaskOrganizer; private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); @@ -55,8 +57,10 @@ public final class ShellCommandHandlerImpl { Optional oneHandedOptional, Optional hideDisplayCutout, Optional appPairsOptional, + Optional recentTasks, ShellExecutor mainExecutor) { mShellTaskOrganizer = shellTaskOrganizer; + mRecentTasks = recentTasks; mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mPipOptional = pipOptional; @@ -85,6 +89,9 @@ public final class ShellCommandHandlerImpl { pw.println(); pw.println(); mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, "")); + pw.println(); + pw.println(); + mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, "")); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index fa58fcda3d3b5..f5678776ed78d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -29,8 +29,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.fullscreen.FullscreenUnfoldController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; @@ -49,7 +49,6 @@ public class ShellInitImpl { private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional mBubblesOptional; - private final Optional mLegacySplitScreenOptional; private final Optional mSplitScreenOptional; private final Optional mAppPairsOptional; private final Optional mPipTouchHandlerOptional; @@ -59,6 +58,7 @@ public class ShellInitImpl { private final ShellExecutor mMainExecutor; private final Transitions mTransitions; private final StartingWindowController mStartingWindow; + private final Optional mRecentTasks; private final InitImpl mImpl = new InitImpl(); @@ -69,13 +69,13 @@ public class ShellInitImpl { DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional bubblesOptional, - Optional legacySplitScreenOptional, Optional splitScreenOptional, Optional appPairsOptional, Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional fullscreenUnfoldTransitionController, Optional> freeformTaskListenerOptional, + Optional recentTasks, Transitions transitions, StartingWindowController startingWindow, ShellExecutor mainExecutor) { @@ -85,13 +85,13 @@ public class ShellInitImpl { mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; mBubblesOptional = bubblesOptional; - mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; mFullscreenUnfoldController = fullscreenUnfoldTransitionController; mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f); + mRecentTasks = recentTasks; mTransitions = transitions; mMainExecutor = mainExecutor; mStartingWindow = startingWindow; @@ -135,6 +135,7 @@ public class ShellInitImpl { f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); + mRecentTasks.ifPresent(RecentTasksController::init); } @ExternalThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 020ecb7186ed4..75bc461253249 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -51,6 +51,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.startingsurface.StartingWindowController; @@ -59,6 +60,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; /** @@ -150,20 +152,34 @@ public class ShellTaskOrganizer extends TaskOrganizer implements @Nullable private final SizeCompatUIController mSizeCompatUI; + @Nullable + private final Optional mRecentTasks; + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { - this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */); + this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, + Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable SizeCompatUIController sizeCompatUI) { - this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI); + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + Optional.empty() /* recentTasksController */); + } + + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable + SizeCompatUIController sizeCompatUI, + Optional recentTasks) { + this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI, + recentTasks); } @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable SizeCompatUIController sizeCompatUI) { + Context context, @Nullable SizeCompatUIController sizeCompatUI, + Optional recentTasks) { super(taskOrganizerController, mainExecutor); mSizeCompatUI = sizeCompatUI; + mRecentTasks = recentTasks; if (sizeCompatUI != null) { sizeCompatUI.setSizeCompatUICallback(this); } @@ -401,6 +417,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements // Notify the size compat UI if the listener or task info changed. notifySizeCompatUI(taskInfo, newListener); } + if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) { + // Notify the recent tasks when a task changes windowing modes + mRecentTasks.ifPresent(recentTasks -> + recentTasks.onTaskWindowingModeChanged(taskInfo)); + } } } @@ -428,6 +449,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements notifyLocusVisibilityIfNeeded(taskInfo); // Pass null for listener to remove the size compat UI on this task if there is any. notifySizeCompatUI(taskInfo, null /* taskListener */); + // Notify the recent tasks that a task has been removed + mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl new file mode 100644 index 0000000000000..6e78fcba4a00c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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.wm.shell.recents; + +import com.android.wm.shell.recents.IRecentTasksListener; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +/** + * Interface that is exposed to remote callers to fetch recent tasks. + */ +interface IRecentTasks { + + /** + * Registers a recent tasks listener. + */ + oneway void registerRecentTasksListener(in IRecentTasksListener listener) = 1; + + /** + * Unregisters a recent tasks listener. + */ + oneway void unregisterRecentTasksListener(in IRecentTasksListener listener) = 2; + + /** + * Gets the set of recent tasks. + */ + GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl new file mode 100644 index 0000000000000..8efa42830d80f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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 distshellributed 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.wm.shell.recents; + +/** + * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. + */ +oneway interface IRecentTasksListener { + + /** + * Called when the set of recent tasks change. + */ + void onRecentTasksChanged(); +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java new file mode 100644 index 0000000000000..a5748f69388f9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.wm.shell.recents; + +import com.android.wm.shell.common.annotations.ExternalThread; + +/** + * Interface for interacting with the recent tasks. + */ +@ExternalThread +public interface RecentTasks { + /** + * Returns a binder that can be passed to an external process to fetch recent tasks. + */ + default IRecentTasks createExternalInterface() { + return null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java new file mode 100644 index 0000000000000..836a6f610bbd1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2021 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.wm.shell.recents; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.TaskInfo; +import android.content.Context; +import android.os.RemoteException; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import androidx.annotation.BinderThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages the recent task list from the system, caching it as necessary. + */ +public class RecentTasksController implements TaskStackListenerCallback, + RemoteCallable { + private static final String TAG = RecentTasksController.class.getSimpleName(); + + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final TaskStackListenerImpl mTaskStackListener; + private final RecentTasks mImpl = new RecentTasksImpl(); + + private final ArrayList mCallbacks = new ArrayList<>(); + // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a + // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) + private final SparseIntArray mSplitTasks = new SparseIntArray(); + + /** + * Creates {@link RecentTasksController}, returns {@code null} if the feature is not + * supported. + */ + @Nullable + public static RecentTasksController create( + Context context, + TaskStackListenerImpl taskStackListener, + @ShellMainThread ShellExecutor mainExecutor + ) { + if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { + return null; + } + return new RecentTasksController(context, taskStackListener, mainExecutor); + } + + RecentTasksController(Context context, TaskStackListenerImpl taskStackListener, + ShellExecutor mainExecutor) { + mContext = context; + mTaskStackListener = taskStackListener; + mMainExecutor = mainExecutor; + } + + public RecentTasks asRecentTasks() { + return mImpl; + } + + public void init() { + mTaskStackListener.addListener(this); + } + + /** + * Adds a split pair. This call does not validate the taskIds, only that they are not the same. + */ + public void addSplitPair(int taskId1, int taskId2) { + if (taskId1 == taskId2) { + return; + } + // Remove any previous pairs + removeSplitPair(taskId1); + removeSplitPair(taskId2); + mSplitTasks.put(taskId1, taskId2); + mSplitTasks.put(taskId2, taskId1); + } + + /** + * Removes a split pair. + */ + public void removeSplitPair(int taskId) { + int pairedTaskId = mSplitTasks.get(taskId, INVALID_TASK_ID); + if (pairedTaskId != INVALID_TASK_ID) { + mSplitTasks.delete(taskId); + mSplitTasks.delete(pairedTaskId); + } + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + @Override + public void onTaskStackChanged() { + notifyRecentTasksChanged(); + } + + @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. + notifyRecentTasksChanged(); + } + + public void onTaskRemoved(TaskInfo taskInfo) { + // Remove any split pairs associated with this task + removeSplitPair(taskInfo.taskId); + notifyRecentTasksChanged(); + } + + public void onTaskWindowingModeChanged(TaskInfo taskInfo) { + notifyRecentTasksChanged(); + } + + @VisibleForTesting + void notifyRecentTasksChanged() { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).run(); + } + } + + private void registerRecentTasksListener(Runnable listener) { + if (!mCallbacks.contains(listener)) { + mCallbacks.add(listener); + } + } + + private void unregisterRecentTasksListener(Runnable listener) { + mCallbacks.remove(listener); + } + + @VisibleForTesting + List getRawRecentTasks(int maxNum, int flags, int userId) { + return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId); + } + + @VisibleForTesting + ArrayList getRecentTasks(int maxNum, int flags, int userId) { + // Note: the returned task list is from the most-recent to least-recent order + final List rawList = getRawRecentTasks(maxNum, flags, + userId); + + // Make a mapping of task id -> task info + final SparseArray rawMapping = new SparseArray<>(); + for (int i = 0; i < rawList.size(); i++) { + final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + rawMapping.put(taskInfo.taskId, taskInfo); + } + + // Pull out the pairs as we iterate back in the list + ArrayList recentTasks = new ArrayList<>(); + for (int i = 0; i < rawList.size(); i++) { + final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + if (!rawMapping.contains(taskInfo.taskId)) { + // If it's not in the mapping, then it was already paired with another task + continue; + } + + final int pairedTaskId = mSplitTasks.get(taskInfo.taskId); + if (pairedTaskId != INVALID_TASK_ID) { + final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + rawMapping.remove(pairedTaskId); + recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo)); + } else { + recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); + } + } + return recentTasks; + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + ArrayList recentTasks = getRecentTasks(Integer.MAX_VALUE, + ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); + for (int i = 0; i < recentTasks.size(); i++) { + pw.println(innerPrefix + recentTasks.get(i)); + } + } + + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread + private class RecentTasksImpl implements RecentTasks { + private IRecentTasksImpl mIRecentTasks; + + @Override + public IRecentTasks createExternalInterface() { + if (mIRecentTasks != null) { + mIRecentTasks.invalidate(); + } + mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this); + return mIRecentTasks; + } + } + + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class IRecentTasksImpl extends IRecentTasks.Stub { + private RecentTasksController mController; + private final SingleInstanceRemoteListener mListener; + private final Runnable mRecentTasksListener = + new Runnable() { + @Override + public void run() { + mListener.call(l -> l.onRecentTasksChanged()); + } + }; + + public IRecentTasksImpl(RecentTasksController controller) { + mController = controller; + mListener = new SingleInstanceRemoteListener<>(controller, + c -> c.registerRecentTasksListener(mRecentTasksListener), + c -> c.unregisterRecentTasksListener(mRecentTasksListener)); + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public void registerRecentTasksListener(IRecentTasksListener listener) + throws RemoteException { + executeRemoteCallWithTaskPermission(mController, "registerRecentTasksListener", + (controller) -> mListener.register(listener)); + } + + @Override + public void unregisterRecentTasksListener(IRecentTasksListener listener) + throws RemoteException { + executeRemoteCallWithTaskPermission(mController, "unregisterRecentTasksListener", + (controller) -> mListener.unregister()); + } + + @Override + public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + throws RemoteException { + final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + executeRemoteCallWithTaskPermission(mController, "getRecentTasks", + (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) + .toArray(new GroupedRecentTaskInfo[0]), + true /* blocking */); + return out[0]; + } + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl new file mode 100644 index 0000000000000..15797cdb9abac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.wm.shell.util; + +parcelable GroupedRecentTaskInfo; \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java new file mode 100644 index 0000000000000..0331ba19defe0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 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.wm.shell.util; + +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Simple container for recent tasks. May contain either a single or pair of tasks. + */ +public class GroupedRecentTaskInfo implements Parcelable { + public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; + public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; + + public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { + this(task1, null); + } + + public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, + @Nullable ActivityManager.RecentTaskInfo task2) { + mTaskInfo1 = task1; + mTaskInfo2 = task2; + } + + GroupedRecentTaskInfo(Parcel parcel) { + mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + } + + @Override + public String toString() { + return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); + } + + private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + if (taskInfo == null) { + return null; + } + return "id=" + taskInfo.taskId + + " baseIntent=" + (taskInfo.baseIntent != null + ? taskInfo.baseIntent.getComponent() + : "null") + + " winMode=" + WindowConfiguration.windowingModeToString( + taskInfo.getWindowingMode()); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedObject(mTaskInfo1, flags); + parcel.writeTypedObject(mTaskInfo2, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public GroupedRecentTaskInfo createFromParcel(Parcel source) { + return new GroupedRecentTaskInfo(source); + } + public GroupedRecentTaskInfo[] newArray(int size) { + return new GroupedRecentTaskInfo[size]; + } + }; +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index d5acbbcf7d2c7..1fcbf14fb7328 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -65,6 +65,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Optional; /** * Tests for the shell task organizer. @@ -131,7 +132,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mSizeCompatUI)); + mSizeCompatUI, Optional.empty())); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 5bdf831a81f44..6080f3ae78e85 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -26,6 +26,7 @@ import androidx.test.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.mockito.MockitoAnnotations; /** * Base class that does shell test case setup. @@ -36,6 +37,7 @@ public abstract class ShellTestCase { @Before public void shellSetup() { + MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java new file mode 100644 index 0000000000000..a1e12319ac702 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2021 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.wm.shell.recents; + +import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import static java.lang.Integer.MAX_VALUE; + +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.content.Context; +import android.view.SurfaceControl; +import android.window.TaskAppearedInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.util.GroupedRecentTaskInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; + +/** + * Tests for {@link RecentTasksController}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RecentTasksControllerTest extends ShellTestCase { + + @Mock + private Context mContext; + @Mock + private TaskStackListenerImpl mTaskStackListener; + + private ShellTaskOrganizer mShellTaskOrganizer; + private RecentTasksController mRecentTasksController; + private ShellExecutor mMainExecutor; + + @Before + public void setUp() { + mMainExecutor = new TestShellExecutor(); + mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener, + mMainExecutor)); + mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext, + null /* sizeCompatUI */, Optional.of(mRecentTasksController)); + } + + @Test + public void testGetRecentTasks() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + ArrayList recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, -1, + t3.taskId, -1); + } + + @Test + public void testGetRecentTasks_withPairs() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + setRawList(t1, t2, t3, t4, t5, t6); + + // Mark a couple pairs [t2, t4], [t3, t5] + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId); + + ArrayList recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + assertGroupedTasksListEquals(recentTasks, + t1.taskId, -1, + t2.taskId, t4.taskId, + t3.taskId, t5.taskId, + t6.taskId, -1); + } + + @Test + public void testRemovedTaskRemovesSplit() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + setRawList(t1, t2, t3); + + // Add a pair + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId); + reset(mRecentTasksController); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2); + mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash); + mShellTaskOrganizer.onTaskVanished(rt2); + + verify(mRecentTasksController).removeSplitPair(t2.taskId); + } + + @Test + public void testTaskWindowingModeChangedNotifiesChange() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + setRawList(t1); + + // Remove one of the tasks and ensure the pair is removed + SurfaceControl mockLeash = mock(SurfaceControl.class); + ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2); + rt2Fullscreen.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_FULLSCREEN); + mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash); + + // Change the windowing mode and ensure the recent tasks change is notified + ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2); + rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW); + mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow); + + verify(mRecentTasksController).notifyRecentTasksChanged(); + } + + /** + * Helper to create a task with a given task id. + */ + private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { + ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to create a running task with a given task id. + */ + private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) { + ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); + info.taskId = taskId; + return info; + } + + /** + * Helper to set the raw task list on the controller. + */ + private ArrayList setRawList( + ActivityManager.RecentTaskInfo... tasks) { + ArrayList rawList = new ArrayList<>(); + for (ActivityManager.RecentTaskInfo task : tasks) { + rawList.add(task); + } + doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(), + anyInt()); + return rawList; + } + + /** + * Asserts that the recent tasks matches the given task ids. + * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in + * the grouped task list + */ + private void assertGroupedTasksListEquals(ArrayList recentTasks, + int... expectedTaskIds) { + int[] flattenedTaskIds = new int[recentTasks.size() * 2]; + for (int i = 0; i < recentTasks.size(); i++) { + GroupedRecentTaskInfo pair = recentTasks.get(i); + flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null + ? pair.mTaskInfo2.taskId + : -1; + } + assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + + " Received: " + Arrays.toString(flattenedTaskIds), + Arrays.equals(flattenedTaskIds, expectedTaskIds)); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java new file mode 100644 index 0000000000000..323b20e41a5c2 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.systemui.shared.recents.model; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * A group task in the recent tasks list. + * TODO: Move this into Launcher + */ +public class GroupTask { + public @NonNull Task task1; + public @Nullable Task task2; + + public GroupTask(@NonNull Task t1, @Nullable Task t2) { + task1 = t1; + task2 = t2; + } + + public GroupTask(@NonNull GroupTask group) { + task1 = new Task(group.task1); + task2 = group.task2 != null + ? new Task(group.task2) + : null; + } + + public boolean containsTask(int taskId) { + return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index e9e9b2421d4a8..3f2ff742d0722 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -38,6 +38,7 @@ import java.util.Objects; /** * A task in the recent tasks list. + * TODO: Move this into Launcher or see if we can remove now */ public class Task { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index d447b485f33b8..d182399239cc9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -57,6 +57,8 @@ public class QuickStepContract { "extra_shell_starting_window"; // See ISmartspaceTransitionController.aidl public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition"; + // See IRecentTasks.aidl + public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks"; public static final String NAV_BAR_MODE_2BUTTON_OVERLAY = WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index f4f99b7b8d39e..e96e924d557f6 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -32,6 +32,7 @@ import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvid import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.recents.RecentTasks; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -120,7 +121,8 @@ public class SystemUIFactory { .setTransitions(mWMComponent.getTransitions()) .setStartingSurface(mWMComponent.getStartingSurface()) .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) - .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()); + .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) + .setRecentTasks(mWMComponent.getRecentTasks()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -137,7 +139,8 @@ public class SystemUIFactory { .setTransitions(new ShellTransitions() {}) .setDisplayAreaHelper(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null)) - .setTaskSurfaceHelper(Optional.ofNullable(null)); + .setTaskSurfaceHelper(Optional.ofNullable(null)) + .setRecentTasks(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index a9a4da8d94a21..8993af2174c47 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -34,6 +34,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -101,6 +102,9 @@ public interface SysUIComponent { @BindsInstance Builder setTaskSurfaceHelper(Optional t); + @BindsInstance + Builder setRecentTasks(Optional r); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 618c26b891968..634349ec7ea11 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,6 +32,7 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; @@ -110,4 +111,7 @@ public interface WMComponent { @WMSingleton Optional getTaskSurfaceHelper(); + + @WMSingleton + Optional getRecentTasks(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index bf7577c1d8303..fa874b19c2ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; +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_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -105,6 +106,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.transition.ShellTransitions; @@ -158,6 +160,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final ShellTransitions mShellTransitions; private final Optional mStartingSurface; private final SmartspaceTransitionController mSmartspaceTransitionController; + private final Optional mRecentTasks; private Region mActiveNavBarRegion; @@ -491,6 +494,9 @@ public class OverviewProxyService extends CurrentUserTracker implements params.putBinder( KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER, mSmartspaceTransitionController.createExternalInterface().asBinder()); + mRecentTasks.ifPresent(recentTasks -> params.putBinder( + KEY_EXTRA_RECENT_TASKS, + recentTasks.createExternalInterface().asBinder())); try { mOverviewProxy.onInitialize(params); @@ -541,17 +547,18 @@ public class OverviewProxyService extends CurrentUserTracker implements @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, Lazy navBarControllerLazy, + Lazy> statusBarOptionalLazy, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Optional pipOptional, Optional legacySplitScreenOptional, Optional splitScreenOptional, - Lazy> statusBarOptionalLazy, Optional oneHandedOptional, + Optional recentTasks, + Optional startingSurface, BroadcastDispatcher broadcastDispatcher, ShellTransitions shellTransitions, ScreenLifecycle screenLifecycle, - Optional startingSurface, SmartspaceTransitionController smartspaceTransitionController, DumpManager dumpManager) { super(broadcastDispatcher); @@ -573,6 +580,7 @@ public class OverviewProxyService extends CurrentUserTracker implements mSysUiState.addCallback(this::notifySystemUiStateFlags); mOneHandedOptional = oneHandedOptional; mShellTransitions = shellTransitions; + mRecentTasks = recentTasks; // Assumes device always starts with back button until launcher tells it that it does not mNavBarButtonAlpha = 1.0f; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 0a33930d404cf..bdc62c974f5a5 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -74,6 +74,8 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipTouchHandler; +import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -144,8 +146,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, - Context context, SizeCompatUIController sizeCompatUI) { - return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI); + Context context, + SizeCompatUIController sizeCompatUI, + Optional recentTasksOptional + ) { + return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI, recentTasksOptional); } @WMSingleton @@ -409,6 +414,28 @@ public abstract class WMShellBaseModule { @BindsOptionalOf abstract PipTouchHandler optionalPipTouchHandler(); + // + // Recent tasks + // + + @WMSingleton + @Provides + static Optional provideRecentTasks( + Optional recentTasksController) { + return recentTasksController.map((controller) -> controller.asRecentTasks()); + } + + @WMSingleton + @Provides + static Optional provideRecentTasksController( + Context context, + TaskStackListenerImpl taskStackListener, + @ShellMainThread ShellExecutor mainExecutor + ) { + return Optional.ofNullable( + RecentTasksController.create(context, taskStackListener, mainExecutor)); + } + // // Shell transitions // @@ -555,13 +582,13 @@ public abstract class WMShellBaseModule { DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional bubblesOptional, - Optional legacySplitScreenOptional, Optional splitScreenOptional, Optional appPairsOptional, Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional appUnfoldTransitionController, Optional> freeformTaskListener, + Optional recentTasksOptional, Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { @@ -571,13 +598,13 @@ public abstract class WMShellBaseModule { dragAndDropController, shellTaskOrganizer, bubblesOptional, - legacySplitScreenOptional, splitScreenOptional, appPairsOptional, pipTouchHandlerOptional, fullscreenTaskListener, appUnfoldTransitionController, freeformTaskListener, + recentTasksOptional, transitions, startingWindow, mainExecutor); @@ -603,9 +630,10 @@ public abstract class WMShellBaseModule { Optional oneHandedOptional, Optional hideDisplayCutout, Optional appPairsOptional, + Optional recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, appPairsOptional, mainExecutor); + hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); } }