Loading core/java/android/app/TaskInfo.java +8 −0 Original line number Original line Diff line number Diff line Loading @@ -375,6 +375,14 @@ public class TaskInfo { readTaskFromParcel(source); readTaskFromParcel(source); } } /** * Returns the task id. * @hide */ public int getTaskId() { return taskId; } /** /** * Whether this task is visible. * Whether this task is visible. */ */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +186 −49 Original line number Original line Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.graphics.Point; import android.os.Bundle; import android.os.Bundle; import android.os.RemoteException; import android.os.RemoteException; import android.util.Slog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseIntArray; import android.window.DesktopModeFlags; import android.window.DesktopModeFlags; import android.window.WindowContainerToken; import android.window.WindowContainerToken; Loading Loading @@ -83,6 +82,7 @@ import java.util.Optional; import java.util.Set; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Consumer; import java.util.stream.Collectors; /** /** * Manages the recent task list from the system, caching it as necessary. * Manages the recent task list from the system, caching it as necessary. Loading Loading @@ -124,6 +124,10 @@ public class RecentTasksController implements TaskStackListenerCallback, * Cached list of the visible tasks, sorted from top most to bottom most. * Cached list of the visible tasks, sorted from top most to bottom most. */ */ private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>(); private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>(); private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>(); // Temporary vars used in `generateList()` private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>(); /** /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not * Creates {@link RecentTasksController}, returns {@code null} if the feature is not Loading Loading @@ -348,8 +352,11 @@ public class RecentTasksController implements TaskStackListenerCallback, public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) { public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) { mVisibleTasks.clear(); mVisibleTasks.clear(); mVisibleTasks.addAll(visibleTasks); mVisibleTasks.addAll(visibleTasks); mVisibleTasksMap.clear(); mVisibleTasksMap.putAll(mVisibleTasks.stream().collect( Collectors.toMap(TaskInfo::getTaskId, task -> task))); // Notify with all the info and not just the running task info // Notify with all the info and not just the running task info notifyVisibleTasksChanged(visibleTasks); notifyVisibleTasksChanged(mVisibleTasks); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -458,7 +465,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } } try { try { // Compute the visible recent tasks in order, and move the task to the top // Compute the visible recent tasks in order, and move the task to the top mListener.onVisibleTasksChanged(generateList(visibleTasks) mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged") .toArray(new GroupedTaskInfo[0])); .toArray(new GroupedTaskInfo[0])); } catch (RemoteException e) { } catch (RemoteException e) { Slog.w(TAG, "Failed call onVisibleTasksChanged", e); Slog.w(TAG, "Failed call onVisibleTasksChanged", e); Loading Loading @@ -494,40 +501,87 @@ public class RecentTasksController implements TaskStackListenerCallback, @VisibleForTesting @VisibleForTesting ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is ordered from the most-recent to least-recent order // Note: the returned task list is ordered from the most-recent to least-recent order return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId)); return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId), "getRecentTasks"); } } /** /** * Generates a list of GroupedTaskInfos for the given list of tasks. * Returns whether the given task should be excluded from the generated list. */ */ private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) { private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) { // Make a mapping of task id -> task info if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); // We don't current send pinned tasks as a part of recent or running tasks for (int i = 0; i < tasks.size(); i++) { return true; final TaskInfo taskInfo = tasks.get(i); } rawMapping.put(taskInfo.taskId, taskInfo); if (isWallpaperTask(taskInfo)) { // Don't add the fullscreen wallpaper task as an entry in grouped tasks return true; } return false; } } ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); /** Set<Integer> minimizedFreeformTasks = new HashSet<>(); * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or * running tasks). * * The general flow is: * - Collect the desktop tasks * - Collect the visible tasks (in order), including the desktop tasks if visible * - Construct the final list with the visible tasks, followed by the subsequent tasks * - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into * a single mixed task * - if the desktop tasks are not visible, they will be appended to the end of the list * * TODO(346588978): Generate list in per-display order * * @param tasks The list of tasks ordered from most recent to least recent */ @VisibleForTesting <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks, String reason) { if (tasks.isEmpty()) { return new ArrayList<>(); } int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; if (enableShellTopTaskTracking()) { ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason); } ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(); // Make a mapping of task id -> task info for the remaining tasks to be processed, this // Pull out the pairs as we iterate back in the list // mapping is used to keep track of split tasks that may exist later in the task list that // should be ignored because they've already been grouped mTmpRemaining.clear(); mTmpRemaining.putAll(tasks.stream().collect( Collectors.toMap(TaskInfo::getTaskId, task -> task))); // The final grouped tasks ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size()); ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>(); // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections // here as the GroupedTaskInfo can store them without copying ArrayList<TaskInfo> desktopTasks = new ArrayList<>(); Set<Integer> minimizedDesktopTasks = new HashSet<>(); boolean desktopTasksVisible = false; for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) { final TaskInfo taskInfo = tasks.get(i); final TaskInfo taskInfo = tasks.get(i); if (!rawMapping.contains(taskInfo.taskId)) { final int taskId = taskInfo.taskId; // If it's not in the mapping, then it was already paired with another task if (!mTmpRemaining.containsKey(taskInfo.taskId)) { // Skip if we've already processed it continue; continue; } } if (excludeTaskFromGeneratedList(taskInfo)) { // Skip and update the list if we are excluding this task mTmpRemaining.remove(taskId); continue; } // Desktop tasks if (DesktopModeStatus.canEnterDesktopMode(mContext) && if (DesktopModeStatus.canEnterDesktopMode(mContext) && mDesktopUserRepositories.isPresent() mDesktopUserRepositories.isPresent() && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) { && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) { // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = groupedTasks.size(); } // If task has their app bounds set to null which happens after reboot, set the // If task has their app bounds set to null which happens after reboot, set the // app bounds to persisted lastFullscreenBounds. Also set the position in parent // app bounds to persisted lastFullscreenBounds. Also set the position in parent // to the top left of the bounds. // to the top left of the bounds. Loading @@ -538,48 +592,131 @@ public class RecentTasksController implements TaskStackListenerCallback, taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left, taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left, taskInfo.lastNonFullscreenBounds.top); taskInfo.lastNonFullscreenBounds.top); } } freeformTasks.add(taskInfo); desktopTasks.add(taskInfo); if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) { if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) { minimizedFreeformTasks.add(taskInfo.taskId); minimizedDesktopTasks.add(taskId); } } desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId); mTmpRemaining.remove(taskId); continue; continue; } } final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (enableShellTopTaskTracking()) { if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) { // Visible tasks final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); if (mVisibleTasksMap.containsKey(taskId)) { rawMapping.remove(pairedTaskId); // Split tasks groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, mTaskSplitBoundsMap.get(pairedTaskId))); visibleGroupedTasks)) { continue; } // Fullscreen tasks visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); mTmpRemaining.remove(taskId); } } else { } else { if (isWallpaperTask(taskInfo)) { // Split tasks // Don't add the wallpaper task as an entry in grouped tasks if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) { continue; continue; } } // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same // grouped task // Fullscreen tasks groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } } } // Add a special entry for freeform tasks if (enableShellTopTaskTracking()) { if (!freeformTasks.isEmpty()) { // Phase 2: If there were desktop tasks and they are visible, add them to the visible groupedTasks.add(mostRecentFreeformTaskIndex, // list as well (the actual order doesn't matter for Overview) GroupedTaskInfo.forFreeformTasks( if (!desktopTasks.isEmpty() && desktopTasksVisible) { freeformTasks, visibleGroupedTasks.add( minimizedFreeformTasks)); GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } if (!visibleGroupedTasks.isEmpty()) { // Phase 3: Combine the visible tasks into a single mixed grouped task, only if // there are > 1 tasks to group, and add them to the final list if (visibleGroupedTasks.size() > 1) { groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks)); } else { groupedTasks.addAll(visibleGroupedTasks); } } } dumpGroupedTasks(groupedTasks, "Phase 3"); if (enableShellTopTaskTracking()) { // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks // We don't current send pinned tasks as a part of recent or running tasks, so remove // in order to the final list // them from the list here for (int i = 0; i < tasks.size(); i++) { groupedTasks.removeIf( final TaskInfo taskInfo = tasks.get(i); gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED); if (!mTmpRemaining.containsKey(taskInfo.taskId)) { // Skip if we've already processed it continue; } // Split tasks if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) { continue; } // Fullscreen tasks groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } dumpGroupedTasks(groupedTasks, "Phase 4"); // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added // above), add them to the end of the final list (the actual order doesn't // matter for Overview) if (!desktopTasks.isEmpty() && !desktopTasksVisible) { groupedTasks.add( GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } dumpGroupedTasks(groupedTasks, "Phase 5"); } else { // Add the desktop tasks at the end of the list if (!desktopTasks.isEmpty()) { groupedTasks.add( GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } } } return groupedTasks; return groupedTasks; } } /** * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task, * then a split grouped task with the pair is added to {@param tasksOut}. * * @return whether a split task was extracted and added to the given list */ private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo, @NonNull Map<Integer, TaskInfo> remainingTasks, @NonNull ArrayList<GroupedTaskInfo> tasksOut) { final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) { return false; } // Add both this task and its pair to the list, and mark the paired task to be // skipped when it is encountered in the list final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId); remainingTasks.remove(taskInfo.taskId); remainingTasks.remove(pairedTaskId); tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); return true; } /** Dumps the set of tasks to protolog */ private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) { if (!WM_SHELL_TASK_OBSERVER.isEnabled()) { return; } ProtoLog.v(WM_SHELL_TASK_OBSERVER, " Tasks (%s):", reason); for (GroupedTaskInfo task : groupedTasks) { ProtoLog.v(WM_SHELL_TASK_OBSERVER, " %s", task); } } /** /** * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified. * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified. * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task. * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task. Loading libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +10 −8 Original line number Original line Diff line number Diff line Loading @@ -193,7 +193,10 @@ class TaskStackTransitionObserver( override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { if (enableShellTopTaskTracking()) { if (!enableShellTopTaskTracking()) { return } if (pendingCloseTasks.isNotEmpty()) { if (pendingCloseTasks.isNotEmpty()) { // Update the visible task list based on the pending close tasks // Update the visible task list based on the pending close tasks for (change in pendingCloseTasks) { for (change in pendingCloseTasks) { Loading @@ -204,7 +207,6 @@ class TaskStackTransitionObserver( updateVisibleTasksList("transition-finished") updateVisibleTasksList("transition-finished") } } } } } override fun onTaskVanished(taskInfo: RunningTaskInfo?) { override fun onTaskVanished(taskInfo: RunningTaskInfo?) { if (!enableShellTopTaskTracking()) { if (!enableShellTopTaskTracking()) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +165 −69 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +4 −3 Original line number Original line Diff line number Diff line Loading @@ -57,6 +57,7 @@ constructor( // activity and a null second task, so the foreground task will be index 1, but when // activity and a null second task, so the foreground task will be index 1, but when // opening the app selector in split screen mode, the foreground task will be the second // opening the app selector in split screen mode, the foreground task will be the second // task in index 0. // task in index 0. // TODO(346588978): This needs to be updated for mixed groups val foregroundGroup = val foregroundGroup = if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first() if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first() else groupedTasks.elementAtOrNull(1) else groupedTasks.elementAtOrNull(1) Loading @@ -69,7 +70,7 @@ constructor( it.taskInfo1, it.taskInfo1, it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible, it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible, userManager.getUserInfo(it.taskInfo1.userId).toUserType(), userManager.getUserInfo(it.taskInfo1.userId).toUserType(), it.splitBounds it.splitBounds, ) ) val task2 = val task2 = Loading @@ -78,7 +79,7 @@ constructor( it.taskInfo2!!, it.taskInfo2!!, it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible, it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible, userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(), userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(), it.splitBounds it.splitBounds, ) ) } else null } else null Loading @@ -92,7 +93,7 @@ constructor( Integer.MAX_VALUE, Integer.MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, RECENT_IGNORE_UNAVAILABLE, userTracker.userId, userTracker.userId, backgroundExecutor backgroundExecutor, ) { tasks -> ) { tasks -> continuation.resume(tasks) continuation.resume(tasks) } } Loading Loading
core/java/android/app/TaskInfo.java +8 −0 Original line number Original line Diff line number Diff line Loading @@ -375,6 +375,14 @@ public class TaskInfo { readTaskFromParcel(source); readTaskFromParcel(source); } } /** * Returns the task id. * @hide */ public int getTaskId() { return taskId; } /** /** * Whether this task is visible. * Whether this task is visible. */ */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +186 −49 Original line number Original line Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.graphics.Point; import android.os.Bundle; import android.os.Bundle; import android.os.RemoteException; import android.os.RemoteException; import android.util.Slog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseIntArray; import android.window.DesktopModeFlags; import android.window.DesktopModeFlags; import android.window.WindowContainerToken; import android.window.WindowContainerToken; Loading Loading @@ -83,6 +82,7 @@ import java.util.Optional; import java.util.Set; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Consumer; import java.util.stream.Collectors; /** /** * Manages the recent task list from the system, caching it as necessary. * Manages the recent task list from the system, caching it as necessary. Loading Loading @@ -124,6 +124,10 @@ public class RecentTasksController implements TaskStackListenerCallback, * Cached list of the visible tasks, sorted from top most to bottom most. * Cached list of the visible tasks, sorted from top most to bottom most. */ */ private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>(); private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>(); private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>(); // Temporary vars used in `generateList()` private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>(); /** /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not * Creates {@link RecentTasksController}, returns {@code null} if the feature is not Loading Loading @@ -348,8 +352,11 @@ public class RecentTasksController implements TaskStackListenerCallback, public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) { public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) { mVisibleTasks.clear(); mVisibleTasks.clear(); mVisibleTasks.addAll(visibleTasks); mVisibleTasks.addAll(visibleTasks); mVisibleTasksMap.clear(); mVisibleTasksMap.putAll(mVisibleTasks.stream().collect( Collectors.toMap(TaskInfo::getTaskId, task -> task))); // Notify with all the info and not just the running task info // Notify with all the info and not just the running task info notifyVisibleTasksChanged(visibleTasks); notifyVisibleTasksChanged(mVisibleTasks); } } @VisibleForTesting @VisibleForTesting Loading Loading @@ -458,7 +465,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } } try { try { // Compute the visible recent tasks in order, and move the task to the top // Compute the visible recent tasks in order, and move the task to the top mListener.onVisibleTasksChanged(generateList(visibleTasks) mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged") .toArray(new GroupedTaskInfo[0])); .toArray(new GroupedTaskInfo[0])); } catch (RemoteException e) { } catch (RemoteException e) { Slog.w(TAG, "Failed call onVisibleTasksChanged", e); Slog.w(TAG, "Failed call onVisibleTasksChanged", e); Loading Loading @@ -494,40 +501,87 @@ public class RecentTasksController implements TaskStackListenerCallback, @VisibleForTesting @VisibleForTesting ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is ordered from the most-recent to least-recent order // Note: the returned task list is ordered from the most-recent to least-recent order return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId)); return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId), "getRecentTasks"); } } /** /** * Generates a list of GroupedTaskInfos for the given list of tasks. * Returns whether the given task should be excluded from the generated list. */ */ private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) { private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) { // Make a mapping of task id -> task info if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); // We don't current send pinned tasks as a part of recent or running tasks for (int i = 0; i < tasks.size(); i++) { return true; final TaskInfo taskInfo = tasks.get(i); } rawMapping.put(taskInfo.taskId, taskInfo); if (isWallpaperTask(taskInfo)) { // Don't add the fullscreen wallpaper task as an entry in grouped tasks return true; } return false; } } ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); /** Set<Integer> minimizedFreeformTasks = new HashSet<>(); * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or * running tasks). * * The general flow is: * - Collect the desktop tasks * - Collect the visible tasks (in order), including the desktop tasks if visible * - Construct the final list with the visible tasks, followed by the subsequent tasks * - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into * a single mixed task * - if the desktop tasks are not visible, they will be appended to the end of the list * * TODO(346588978): Generate list in per-display order * * @param tasks The list of tasks ordered from most recent to least recent */ @VisibleForTesting <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks, String reason) { if (tasks.isEmpty()) { return new ArrayList<>(); } int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; if (enableShellTopTaskTracking()) { ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason); } ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(); // Make a mapping of task id -> task info for the remaining tasks to be processed, this // Pull out the pairs as we iterate back in the list // mapping is used to keep track of split tasks that may exist later in the task list that // should be ignored because they've already been grouped mTmpRemaining.clear(); mTmpRemaining.putAll(tasks.stream().collect( Collectors.toMap(TaskInfo::getTaskId, task -> task))); // The final grouped tasks ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size()); ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>(); // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections // here as the GroupedTaskInfo can store them without copying ArrayList<TaskInfo> desktopTasks = new ArrayList<>(); Set<Integer> minimizedDesktopTasks = new HashSet<>(); boolean desktopTasksVisible = false; for (int i = 0; i < tasks.size(); i++) { for (int i = 0; i < tasks.size(); i++) { final TaskInfo taskInfo = tasks.get(i); final TaskInfo taskInfo = tasks.get(i); if (!rawMapping.contains(taskInfo.taskId)) { final int taskId = taskInfo.taskId; // If it's not in the mapping, then it was already paired with another task if (!mTmpRemaining.containsKey(taskInfo.taskId)) { // Skip if we've already processed it continue; continue; } } if (excludeTaskFromGeneratedList(taskInfo)) { // Skip and update the list if we are excluding this task mTmpRemaining.remove(taskId); continue; } // Desktop tasks if (DesktopModeStatus.canEnterDesktopMode(mContext) && if (DesktopModeStatus.canEnterDesktopMode(mContext) && mDesktopUserRepositories.isPresent() mDesktopUserRepositories.isPresent() && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) { && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) { // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = groupedTasks.size(); } // If task has their app bounds set to null which happens after reboot, set the // If task has their app bounds set to null which happens after reboot, set the // app bounds to persisted lastFullscreenBounds. Also set the position in parent // app bounds to persisted lastFullscreenBounds. Also set the position in parent // to the top left of the bounds. // to the top left of the bounds. Loading @@ -538,48 +592,131 @@ public class RecentTasksController implements TaskStackListenerCallback, taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left, taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left, taskInfo.lastNonFullscreenBounds.top); taskInfo.lastNonFullscreenBounds.top); } } freeformTasks.add(taskInfo); desktopTasks.add(taskInfo); if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) { if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) { minimizedFreeformTasks.add(taskInfo.taskId); minimizedDesktopTasks.add(taskId); } } desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId); mTmpRemaining.remove(taskId); continue; continue; } } final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (enableShellTopTaskTracking()) { if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) { // Visible tasks final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); if (mVisibleTasksMap.containsKey(taskId)) { rawMapping.remove(pairedTaskId); // Split tasks groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, mTaskSplitBoundsMap.get(pairedTaskId))); visibleGroupedTasks)) { continue; } // Fullscreen tasks visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); mTmpRemaining.remove(taskId); } } else { } else { if (isWallpaperTask(taskInfo)) { // Split tasks // Don't add the wallpaper task as an entry in grouped tasks if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) { continue; continue; } } // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same // grouped task // Fullscreen tasks groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } } } // Add a special entry for freeform tasks if (enableShellTopTaskTracking()) { if (!freeformTasks.isEmpty()) { // Phase 2: If there were desktop tasks and they are visible, add them to the visible groupedTasks.add(mostRecentFreeformTaskIndex, // list as well (the actual order doesn't matter for Overview) GroupedTaskInfo.forFreeformTasks( if (!desktopTasks.isEmpty() && desktopTasksVisible) { freeformTasks, visibleGroupedTasks.add( minimizedFreeformTasks)); GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } if (!visibleGroupedTasks.isEmpty()) { // Phase 3: Combine the visible tasks into a single mixed grouped task, only if // there are > 1 tasks to group, and add them to the final list if (visibleGroupedTasks.size() > 1) { groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks)); } else { groupedTasks.addAll(visibleGroupedTasks); } } } dumpGroupedTasks(groupedTasks, "Phase 3"); if (enableShellTopTaskTracking()) { // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks // We don't current send pinned tasks as a part of recent or running tasks, so remove // in order to the final list // them from the list here for (int i = 0; i < tasks.size(); i++) { groupedTasks.removeIf( final TaskInfo taskInfo = tasks.get(i); gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED); if (!mTmpRemaining.containsKey(taskInfo.taskId)) { // Skip if we've already processed it continue; } // Split tasks if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) { continue; } // Fullscreen tasks groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } dumpGroupedTasks(groupedTasks, "Phase 4"); // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added // above), add them to the end of the final list (the actual order doesn't // matter for Overview) if (!desktopTasks.isEmpty() && !desktopTasksVisible) { groupedTasks.add( GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } dumpGroupedTasks(groupedTasks, "Phase 5"); } else { // Add the desktop tasks at the end of the list if (!desktopTasks.isEmpty()) { groupedTasks.add( GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks)); } } } return groupedTasks; return groupedTasks; } } /** * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task, * then a split grouped task with the pair is added to {@param tasksOut}. * * @return whether a split task was extracted and added to the given list */ private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo, @NonNull Map<Integer, TaskInfo> remainingTasks, @NonNull ArrayList<GroupedTaskInfo> tasksOut) { final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) { return false; } // Add both this task and its pair to the list, and mark the paired task to be // skipped when it is encountered in the list final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId); remainingTasks.remove(taskInfo.taskId); remainingTasks.remove(pairedTaskId); tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); return true; } /** Dumps the set of tasks to protolog */ private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) { if (!WM_SHELL_TASK_OBSERVER.isEnabled()) { return; } ProtoLog.v(WM_SHELL_TASK_OBSERVER, " Tasks (%s):", reason); for (GroupedTaskInfo task : groupedTasks) { ProtoLog.v(WM_SHELL_TASK_OBSERVER, " %s", task); } } /** /** * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified. * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified. * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task. * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task. Loading
libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +10 −8 Original line number Original line Diff line number Diff line Loading @@ -193,7 +193,10 @@ class TaskStackTransitionObserver( override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { if (enableShellTopTaskTracking()) { if (!enableShellTopTaskTracking()) { return } if (pendingCloseTasks.isNotEmpty()) { if (pendingCloseTasks.isNotEmpty()) { // Update the visible task list based on the pending close tasks // Update the visible task list based on the pending close tasks for (change in pendingCloseTasks) { for (change in pendingCloseTasks) { Loading @@ -204,7 +207,6 @@ class TaskStackTransitionObserver( updateVisibleTasksList("transition-finished") updateVisibleTasksList("transition-finished") } } } } } override fun onTaskVanished(taskInfo: RunningTaskInfo?) { override fun onTaskVanished(taskInfo: RunningTaskInfo?) { if (!enableShellTopTaskTracking()) { if (!enableShellTopTaskTracking()) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +165 −69 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +4 −3 Original line number Original line Diff line number Diff line Loading @@ -57,6 +57,7 @@ constructor( // activity and a null second task, so the foreground task will be index 1, but when // activity and a null second task, so the foreground task will be index 1, but when // opening the app selector in split screen mode, the foreground task will be the second // opening the app selector in split screen mode, the foreground task will be the second // task in index 0. // task in index 0. // TODO(346588978): This needs to be updated for mixed groups val foregroundGroup = val foregroundGroup = if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first() if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first() else groupedTasks.elementAtOrNull(1) else groupedTasks.elementAtOrNull(1) Loading @@ -69,7 +70,7 @@ constructor( it.taskInfo1, it.taskInfo1, it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible, it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible, userManager.getUserInfo(it.taskInfo1.userId).toUserType(), userManager.getUserInfo(it.taskInfo1.userId).toUserType(), it.splitBounds it.splitBounds, ) ) val task2 = val task2 = Loading @@ -78,7 +79,7 @@ constructor( it.taskInfo2!!, it.taskInfo2!!, it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible, it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible, userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(), userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(), it.splitBounds it.splitBounds, ) ) } else null } else null Loading @@ -92,7 +93,7 @@ constructor( Integer.MAX_VALUE, Integer.MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, RECENT_IGNORE_UNAVAILABLE, userTracker.userId, userTracker.userId, backgroundExecutor backgroundExecutor, ) { tasks -> ) { tasks -> continuation.resume(tasks) continuation.resume(tasks) } } Loading