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

Commit 27918770 authored by Winson Chung's avatar Winson Chung
Browse files

5/ Add mixed-type grouped task info

- This will be used to collect multiple grouped tasks
  (ie. split/desktop + fullscreen) into a single grouped task that can
  be used to represent stacked visible tasks in a subsequent CL
- The mixed grouped task is required to have a base grouped task that
  represents the root visible task (generally all the above tasks
  will be visible, but translucent or transient in the case of transitions)

Bug: 346588978
Flag: com.android.wm.shell.enable_shell_top_task_tracking
Test: atest WMShellUnitTests

Change-Id: Id78723e49fa462ac09e16980b418b64234f1da9a
parent 767ff220
Loading
Loading
Loading
Loading
+135 −47
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@

package com.android.wm.shell.shared;

import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;

import android.annotation.IntDef;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.os.Parcel;
import android.os.Parcelable;

@@ -28,11 +30,14 @@ import androidx.annotation.Nullable;

import com.android.wm.shell.shared.split.SplitBounds;

import kotlin.collections.CollectionsKt;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Simple container for recent tasks which should be presented as a single task within the
@@ -43,11 +48,13 @@ public class GroupedTaskInfo implements Parcelable {
    public static final int TYPE_FULLSCREEN = 1;
    public static final int TYPE_SPLIT = 2;
    public static final int TYPE_FREEFORM = 3;
    public static final int TYPE_MIXED = 4;

    @IntDef(prefix = {"TYPE_"}, value = {
            TYPE_FULLSCREEN,
            TYPE_SPLIT,
            TYPE_FREEFORM
            TYPE_FREEFORM,
            TYPE_MIXED
    })
    public @interface GroupType {}

@@ -64,7 +71,7 @@ public class GroupedTaskInfo implements Parcelable {
     * TYPE_SPLIT: Contains the two split roots of each side
     * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode
     */
    @NonNull
    @Nullable
    protected final List<TaskInfo> mTasks;

    /**
@@ -84,6 +91,14 @@ public class GroupedTaskInfo implements Parcelable {
    @Nullable
    protected final int[] mMinimizedTaskIds;

    /**
     * Only set for TYPE_MIXED.
     *
     * The mixed set of task infos in this group.
     */
    @Nullable
    protected final List<GroupedTaskInfo> mGroupedTasks;

    /**
     * Create new for a stack of fullscreen tasks
     */
@@ -111,18 +126,41 @@ public class GroupedTaskInfo implements Parcelable {
                minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
    }

    /**
     * Create new for a group of grouped task infos, those grouped task infos may not be mixed
     * themselves (ie. multiple depths of mixed grouped task infos are not allowed).
     */
    public static GroupedTaskInfo forMixed(@NonNull List<GroupedTaskInfo> groupedTasks) {
        if (groupedTasks.isEmpty()) {
            throw new IllegalArgumentException("Expected non-empty grouped task list");
        }
        if (groupedTasks.stream().anyMatch(task -> task.mType == TYPE_MIXED)) {
            throw new IllegalArgumentException("Unexpected grouped task list");
        }
        return new GroupedTaskInfo(groupedTasks);
    }

    private GroupedTaskInfo(
            @NonNull List<TaskInfo> tasks,
            @Nullable SplitBounds splitBounds,
            @GroupType int type,
            @Nullable int[] minimizedFreeformTaskIds) {
        mTasks = tasks;
        mGroupedTasks = null;
        mSplitBounds = splitBounds;
        mType = type;
        mMinimizedTaskIds = minimizedFreeformTaskIds;
        ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
    }

    private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) {
        mTasks = null;
        mGroupedTasks = groupedTasks;
        mSplitBounds = null;
        mType = TYPE_MIXED;
        mMinimizedTaskIds = null;
    }

    private void ensureAllMinimizedIdsPresent(
            @NonNull List<TaskInfo> tasks,
            @Nullable int[] minimizedFreeformTaskIds) {
@@ -141,26 +179,47 @@ public class GroupedTaskInfo implements Parcelable {
        for (int i = 0; i < numTasks; i++) {
            mTasks.add(new TaskInfo(parcel));
        }
        mGroupedTasks = parcel.createTypedArrayList(GroupedTaskInfo.CREATOR);
        mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
        mType = parcel.readInt();
        mMinimizedTaskIds = parcel.createIntArray();
    }

    /**
     * Get primary {@link RecentTaskInfo}
     * If TYPE_MIXED, returns the root of the grouped tasks
     * For all other types, returns this task itself
     */
    @NonNull
    public GroupedTaskInfo getBaseGroupedTask() {
        if (mType == TYPE_MIXED) {
            return mGroupedTasks.getFirst();
        }
        return this;
    }

    /**
     * Get primary {@link TaskInfo}.
     *
     * @throws IllegalStateException if the group is TYPE_MIXED.
     */
    @NonNull
    public TaskInfo getTaskInfo1() {
        if (mType == TYPE_MIXED) {
            throw new IllegalStateException("No indexed tasks for a mixed task");
        }
        return mTasks.getFirst();
    }

    /**
     * Get secondary {@link RecentTaskInfo}.
     * Get secondary {@link TaskInfo}, used primarily for TYPE_SPLIT.
     *
     * Used in split screen.
     * @throws IllegalStateException if the group is TYPE_MIXED.
     */
    @Nullable
    public TaskInfo getTaskInfo2() {
        if (mType == TYPE_MIXED) {
            throw new IllegalStateException("No indexed tasks for a mixed task");
        }
        if (mTasks.size() > 1) {
            return mTasks.get(1);
        }
@@ -172,9 +231,7 @@ public class GroupedTaskInfo implements Parcelable {
     */
    @Nullable
    public TaskInfo getTaskById(int taskId) {
        return mTasks.stream()
                .filter(task -> task.taskId == taskId)
                .findFirst().orElse(null);
        return CollectionsKt.firstOrNull(getTaskInfoList(), taskInfo -> taskInfo.taskId == taskId);
    }

    /**
@@ -182,35 +239,59 @@ public class GroupedTaskInfo implements Parcelable {
     */
    @NonNull
    public List<TaskInfo> getTaskInfoList() {
        if (mType == TYPE_MIXED) {
            return CollectionsKt.flatMap(mGroupedTasks, groupedTaskInfo -> groupedTaskInfo.mTasks);
        } else {
            return mTasks;
        }
    }

    /**
     * @return Whether this grouped task contains a task with the given {@code taskId}.
     */
    public boolean containsTask(int taskId) {
        return mTasks.stream()
                .anyMatch((task -> task.taskId == taskId));
        return getTaskById(taskId) != null;
    }

    /**
     * Returns whether the group is of the given type, if this is a TYPE_MIXED group, then returns
     * whether the root task info is of the given type.
     */
    public boolean isBaseType(@GroupType int type) {
        return getBaseGroupedTask().mType == type;
    }

    /**
     * Return {@link SplitBounds} if this is a split screen entry or {@code null}
     * Return {@link SplitBounds} if this is a split screen entry or {@code null}. Only valid for
     * TYPE_SPLIT.
     */
    @Nullable
    public SplitBounds getSplitBounds() {
        if (mType == TYPE_MIXED) {
            throw new IllegalStateException("No split bounds for a mixed task");
        }
        return mSplitBounds;
    }

    /**
     * Get type of this recents entry. One of {@link GroupType}
     * Get type of this recents entry. One of {@link GroupType}.
     * Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about
     *       specific group types
     */
    @Deprecated
    @GroupType
    public int getType() {
        return mType;
    }

    /**
     * Returns the set of minimized task ids, only valid for TYPE_FREEFORM.
     */
    @Nullable
    public int[] getMinimizedTaskIds() {
        if (mType == TYPE_MIXED) {
            throw new IllegalStateException("No minimized task ids for a mixed task");
        }
        return mMinimizedTaskIds;
    }

@@ -222,67 +303,64 @@ public class GroupedTaskInfo implements Parcelable {
        GroupedTaskInfo other = (GroupedTaskInfo) obj;
        return mType == other.mType
                && Objects.equals(mTasks, other.mTasks)
                && Objects.equals(mGroupedTasks, other.mGroupedTasks)
                && Objects.equals(mSplitBounds, other.mSplitBounds)
                && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds));
        return Objects.hash(mType, mTasks, mGroupedTasks, mSplitBounds,
                Arrays.hashCode(mMinimizedTaskIds));
    }

    @Override
    public String toString() {
        StringBuilder taskString = new StringBuilder();
        for (int i = 0; i < mTasks.size(); i++) {
            if (i == 0) {
                taskString.append("Task");
        if (mType == TYPE_MIXED) {
            taskString.append("GroupedTasks=" + mGroupedTasks.stream()
                    .map(GroupedTaskInfo::toString)
                    .collect(Collectors.joining(",\n\t", "[\n\t", "\n]")));
        } else {
                taskString.append(", Task");
            taskString.append("Tasks=" + mTasks.stream()
                    .map(taskInfo -> getTaskInfoDumpString(taskInfo))
                    .collect(Collectors.joining(", ", "[", "]")));
            if (mSplitBounds != null) {
                taskString.append(", SplitBounds=").append(mSplitBounds);
            }
            taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i)));
            taskString.append(", Type=" + typeToString(mType));
            taskString.append(", Minimized Task IDs=" + Arrays.toString(mMinimizedTaskIds));
        }
        if (mSplitBounds != null) {
            taskString.append(", SplitBounds: ").append(mSplitBounds);
        }
        taskString.append(", Type=");
        switch (mType) {
            case TYPE_FULLSCREEN:
                taskString.append("TYPE_FULLSCREEN");
                break;
            case TYPE_SPLIT:
                taskString.append("TYPE_SPLIT");
                break;
            case TYPE_FREEFORM:
                taskString.append("TYPE_FREEFORM");
                break;
        }
        taskString.append(", Minimized Task IDs: ");
        taskString.append(Arrays.toString(mMinimizedTaskIds));
        return taskString.toString();
    }

    private String getTaskInfo(TaskInfo taskInfo) {
    private String getTaskInfoDumpString(TaskInfo taskInfo) {
        if (taskInfo == null) {
            return null;
        }
        final boolean isExcluded = (taskInfo.baseIntent.getFlags()
                & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
        return "id=" + taskInfo.taskId
                + " baseIntent=" +
                        (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null
                                ? taskInfo.baseIntent.getComponent().flattenToString()
                                : "null")
                + " winMode=" + WindowConfiguration.windowingModeToString(
                        taskInfo.getWindowingMode());
                + " winMode=" + windowingModeToString(taskInfo.getWindowingMode())
                + " visReq=" + taskInfo.isVisibleRequested
                + " vis=" + taskInfo.isVisible
                + " excluded=" + isExcluded
                + " baseIntent="
                + (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null
                        ? taskInfo.baseIntent.getComponent().flattenToShortString()
                        : "null");
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        // We don't use the parcel list methods because we want to only write the TaskInfo state
        // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
        parcel.writeInt(mTasks.size());
        for (int i = 0; i < mTasks.size(); i++) {
        final int tasksSize = mTasks != null ? mTasks.size() : 0;
        parcel.writeInt(tasksSize);
        for (int i = 0; i < tasksSize; i++) {
            mTasks.get(i).writeTaskToParcel(parcel, flags);
        }
        parcel.writeTypedList(mGroupedTasks);
        parcel.writeTypedObject(mSplitBounds, flags);
        parcel.writeInt(mType);
        parcel.writeIntArray(mMinimizedTaskIds);
@@ -293,6 +371,16 @@ public class GroupedTaskInfo implements Parcelable {
        return 0;
    }

    private String typeToString(@GroupType int type) {
        return switch (type) {
            case TYPE_FULLSCREEN -> "FULLSCREEN";
            case TYPE_SPLIT -> "SPLIT";
            case TYPE_FREEFORM -> "FREEFORM";
            case TYPE_MIXED -> "MIXED";
            default -> "UNKNOWN";
        };
    }

    public static final Creator<GroupedTaskInfo> CREATOR = new Creator() {
        @Override
        public GroupedTaskInfo createFromParcel(Parcel in) {
+164 −38
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import org.mockito.Mockito.mock

/**
 * Tests for [GroupedTaskInfo]
 * Build & Run: atest WMShellUnitTests:GroupedTaskInfoTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -47,7 +48,7 @@ class GroupedTaskInfoTest : ShellTestCase() {

    @Test
    fun testSingleTask_hasCorrectType() {
        assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN)
        assertThat(singleTaskGroupInfo().isBaseType(TYPE_FULLSCREEN)).isTrue()
    }

    @Test
@@ -66,7 +67,7 @@ class GroupedTaskInfoTest : ShellTestCase() {

    @Test
    fun testSplitTasks_hasCorrectType() {
        assertThat(splitTasksGroupInfo().type).isEqualTo(TYPE_SPLIT)
        assertThat(splitTasksGroupInfo().isBaseType(TYPE_SPLIT)).isTrue()
    }

    @Test
@@ -87,8 +88,8 @@ class GroupedTaskInfoTest : ShellTestCase() {

    @Test
    fun testFreeformTasks_hasCorrectType() {
        assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).type)
            .isEqualTo(TYPE_FREEFORM)
        assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).isBaseType(TYPE_FREEFORM))
            .isTrue()
    }

    @Test
@@ -110,84 +111,156 @@ class GroupedTaskInfoTest : ShellTestCase() {
        }
    }

    @Test
    fun testMixedWithFullscreenBase_hasCorrectType() {
        assertThat(mixedTaskGroupInfoWithFullscreenBase().isBaseType(TYPE_FULLSCREEN)).isTrue()
    }

    @Test
    fun testMixedWithSplitBase_hasCorrectType() {
        assertThat(mixedTaskGroupInfoWithSplitBase().isBaseType(TYPE_SPLIT)).isTrue()
    }

    @Test
    fun testMixedWithFreeformBase_hasCorrectType() {
        assertThat(mixedTaskGroupInfoWithFreeformBase().isBaseType(TYPE_FREEFORM)).isTrue()
    }

    @Test
    fun testMixed_disallowEmptyMixed() {
        assertThrows(IllegalArgumentException::class.java) {
            GroupedTaskInfo.forMixed(listOf())
        }
    }

    @Test
    fun testMixed_disallowNestedMixed() {
        assertThrows(IllegalArgumentException::class.java) {
            GroupedTaskInfo.forMixed(listOf(
                GroupedTaskInfo.forMixed(listOf(singleTaskGroupInfo()))))
        }
    }

    @Test
    fun testMixed_disallowNonMixedAccessors() {
        val mixed = mixedTaskGroupInfoWithFullscreenBase()
        assertThrows(IllegalStateException::class.java) {
            mixed.taskInfo1
        }
        assertThrows(IllegalStateException::class.java) {
            mixed.taskInfo2
        }
        assertThrows(IllegalStateException::class.java) {
            mixed.splitBounds
        }
        assertThrows(IllegalStateException::class.java) {
            mixed.minimizedTaskIds
        }
    }

    @Test
    fun testParcelling_singleTask() {
        val recentTaskInfo = singleTaskGroupInfo()
        val taskInfo = singleTaskGroupInfo()
        val parcel = Parcel.obtain()
        recentTaskInfo.writeToParcel(parcel, 0)
        taskInfo.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)
        // Read the object back from the parcel
        val recentTaskInfoParcel: GroupedTaskInfo =
        val taskInfoFromParcel: GroupedTaskInfo =
            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN)
        assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
        assertThat(recentTaskInfoParcel.taskInfo2).isNull()
        assertThat(taskInfoFromParcel.isBaseType(TYPE_FULLSCREEN)).isTrue()
        assertThat(taskInfoFromParcel.taskInfo1.taskId).isEqualTo(1)
        assertThat(taskInfoFromParcel.taskInfo2).isNull()
    }

    @Test
    fun testParcelling_splitTasks() {
        val recentTaskInfo = splitTasksGroupInfo()
        val taskInfo = splitTasksGroupInfo()
        val parcel = Parcel.obtain()
        recentTaskInfo.writeToParcel(parcel, 0)
        taskInfo.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)
        // Read the object back from the parcel
        val recentTaskInfoParcel: GroupedTaskInfo =
        val taskInfoFromParcel: GroupedTaskInfo =
            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT)
        assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
        assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
        assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2)
        assertThat(recentTaskInfoParcel.splitBounds).isNotNull()
        assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50)
        assertThat(taskInfoFromParcel.isBaseType(TYPE_SPLIT)).isTrue()
        assertThat(taskInfoFromParcel.taskInfo1.taskId).isEqualTo(1)
        assertThat(taskInfoFromParcel.taskInfo2).isNotNull()
        assertThat(taskInfoFromParcel.taskInfo2!!.taskId).isEqualTo(2)
        assertThat(taskInfoFromParcel.splitBounds).isNotNull()
        assertThat(taskInfoFromParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50)
    }

    @Test
    fun testParcelling_freeformTasks() {
        val recentTaskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3))
        val taskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3))
        val parcel = Parcel.obtain()
        recentTaskInfo.writeToParcel(parcel, 0)
        taskInfo.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)
        // Read the object back from the parcel
        val recentTaskInfoParcel: GroupedTaskInfo =
        val taskInfoFromParcel: GroupedTaskInfo =
            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
        assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3)
        assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue()
        assertThat(taskInfoFromParcel.taskInfoList).hasSize(3)
        // Only compare task ids
        val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
            { it?.taskId }, "has taskId of"
        )
        assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
            .containsExactly(1, 2, 3)
        assertThat(taskInfoFromParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
            .containsExactly(1, 2, 3).inOrder()
    }

    @Test
    fun testParcelling_freeformTasks_minimizedTasks() {
        val recentTaskInfo = freeformTasksGroupInfo(
        val taskInfo = freeformTasksGroupInfo(
            freeformTaskIds = arrayOf(1, 2, 3), minimizedTaskIds = arrayOf(2))

        val parcel = Parcel.obtain()
        recentTaskInfo.writeToParcel(parcel, 0)
        taskInfo.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        // Read the object back from the parcel
        val recentTaskInfoParcel: GroupedTaskInfo =
        val taskInfoFromParcel: GroupedTaskInfo =
            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
        assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
        assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
        assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue()
        assertThat(taskInfoFromParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
    }

    @Test
    fun testGetTaskById_singleTasks() {
    fun testParcelling_mixedTasks() {
        val taskInfo = GroupedTaskInfo.forMixed(listOf(
                freeformTasksGroupInfo(freeformTaskIds = arrayOf(4, 5, 6),
                    minimizedTaskIds = arrayOf(5)),
                splitTasksGroupInfo(firstId = 2, secondId = 3),
                singleTaskGroupInfo(id = 1)))

        val parcel = Parcel.obtain()
        taskInfo.writeToParcel(parcel, 0)
        parcel.setDataPosition(0)

        // Read the object back from the parcel
        val taskInfoFromParcel: GroupedTaskInfo =
            GroupedTaskInfo.CREATOR.createFromParcel(parcel)
        assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue()
        assertThat(taskInfoFromParcel.baseGroupedTask.minimizedTaskIds).isEqualTo(
            arrayOf(5).toIntArray())
        for (i in 1..6) {
            assertThat(taskInfoFromParcel.containsTask(i)).isTrue()
        }
        assertThat(taskInfoFromParcel.taskInfoList).hasSize(taskInfo.taskInfoList.size)
    }

    @Test
    fun testTaskProperties_singleTasks() {
        val task1 = createTaskInfo(id = 1234)

        val taskInfo = GroupedTaskInfo.forFullscreenTasks(task1)

        assertThat(taskInfo.getTaskById(1234)).isEqualTo(task1)
        assertThat(taskInfo.containsTask(1234)).isTrue()
        assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1))
    }

    @Test
    fun testGetTaskById_multipleTasks() {
    fun testTaskProperties_splitTasks() {
        val task1 = createTaskInfo(id = 1)
        val task2 = createTaskInfo(id = 2)
        val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
@@ -198,6 +271,41 @@ class GroupedTaskInfoTest : ShellTestCase() {
        assertThat(taskInfo.getTaskById(2)).isEqualTo(task2)
        assertThat(taskInfo.containsTask(1)).isTrue()
        assertThat(taskInfo.containsTask(2)).isTrue()
        assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1, task2))
    }

    @Test
    fun testTaskProperties_freeformTasks() {
        val task1 = createTaskInfo(id = 1)
        val task2 = createTaskInfo(id = 2)

        val taskInfo = GroupedTaskInfo.forFreeformTasks(listOf(task1, task2), setOf())

        assertThat(taskInfo.getTaskById(1)).isEqualTo(task1)
        assertThat(taskInfo.getTaskById(2)).isEqualTo(task2)
        assertThat(taskInfo.containsTask(1)).isTrue()
        assertThat(taskInfo.containsTask(2)).isTrue()
        assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1, task2))
    }

    @Test
    fun testTaskProperties_mixedTasks() {
        val task1 = createTaskInfo(id = 1)
        val task2 = createTaskInfo(id = 2)
        val task3 = createTaskInfo(id = 3)
        val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)

        val splitTasks = GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
        val fullscreenTasks = GroupedTaskInfo.forFullscreenTasks(task3)
        val mixedTasks = GroupedTaskInfo.forMixed(listOf(splitTasks, fullscreenTasks))

        assertThat(mixedTasks.getTaskById(1)).isEqualTo(task1)
        assertThat(mixedTasks.getTaskById(2)).isEqualTo(task2)
        assertThat(mixedTasks.getTaskById(3)).isEqualTo(task3)
        assertThat(mixedTasks.containsTask(1)).isTrue()
        assertThat(mixedTasks.containsTask(2)).isTrue()
        assertThat(mixedTasks.containsTask(3)).isTrue()
        assertThat(mixedTasks.taskInfoList).isEqualTo(listOf(task1, task2, task3))
    }

    private fun createTaskInfo(id: Int) = ActivityManager.RecentTaskInfo().apply {
@@ -205,14 +313,14 @@ class GroupedTaskInfoTest : ShellTestCase() {
        token = WindowContainerToken(mock(IWindowContainerToken::class.java))
    }

    private fun singleTaskGroupInfo(): GroupedTaskInfo {
        val task = createTaskInfo(id = 1)
    private fun singleTaskGroupInfo(id: Int = 1): GroupedTaskInfo {
        val task = createTaskInfo(id)
        return GroupedTaskInfo.forFullscreenTasks(task)
    }

    private fun splitTasksGroupInfo(): GroupedTaskInfo {
        val task1 = createTaskInfo(id = 1)
        val task2 = createTaskInfo(id = 2)
    private fun splitTasksGroupInfo(firstId: Int = 1, secondId: Int = 2): GroupedTaskInfo {
        val task1 = createTaskInfo(firstId)
        val task2 = createTaskInfo(secondId)
        val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
        return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
    }
@@ -225,4 +333,22 @@ class GroupedTaskInfoTest : ShellTestCase() {
            freeformTaskIds.map { createTaskInfo(it) }.toList(),
            minimizedTaskIds.toSet())
    }

    private fun mixedTaskGroupInfoWithFullscreenBase(): GroupedTaskInfo {
        return GroupedTaskInfo.forMixed(listOf(
            singleTaskGroupInfo(id = 1),
            singleTaskGroupInfo(id = 2)))
    }

    private fun mixedTaskGroupInfoWithSplitBase(): GroupedTaskInfo {
        return GroupedTaskInfo.forMixed(listOf(
            splitTasksGroupInfo(firstId = 2, secondId = 3),
            singleTaskGroupInfo(id = 1)))
    }

    private fun mixedTaskGroupInfoWithFreeformBase(): GroupedTaskInfo {
        return GroupedTaskInfo.forMixed(listOf(
            freeformTasksGroupInfo(freeformTaskIds = arrayOf(2, 3, 4)),
            singleTaskGroupInfo(id = 1)))
    }
}
+16 −13

File changed.

Preview size limit exceeded, changes collapsed.