Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 176cd8e3 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "6a/ Group visible tasks into a single mixed grouped task" into main

parents f137973b 6850d5a9
Loading
Loading
Loading
Loading
+8 −0
Original line number Original line Diff line number Diff line
@@ -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.
     */
     */
+186 −49
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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.
@@ -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
@@ -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
@@ -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);
@@ -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.
@@ -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.
+10 −8
Original line number Original line Diff line number Diff line
@@ -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) {
@@ -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()) {
+165 −69

File changed.

Preview size limit exceeded, changes collapsed.

+4 −3
Original line number Original line Diff line number Diff line
@@ -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)
@@ -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 =
@@ -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


@@ -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)
            }
            }