Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +135 −47 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 {} Loading @@ -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; /** Loading @@ -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 */ Loading Loading @@ -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) { Loading @@ -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); } Loading @@ -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); } /** Loading @@ -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; } Loading @@ -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); Loading @@ -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) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +164 −38 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import org.mockito.Mockito.mock /** * Tests for [GroupedTaskInfo] * Build & Run: atest WMShellUnitTests:GroupedTaskInfoTest */ @SmallTest @RunWith(AndroidTestingRunner::class) Loading @@ -47,7 +48,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) assertThat(singleTaskGroupInfo().isBaseType(TYPE_FULLSCREEN)).isTrue() } @Test Loading @@ -66,7 +67,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSplitTasks_hasCorrectType() { assertThat(splitTasksGroupInfo().type).isEqualTo(TYPE_SPLIT) assertThat(splitTasksGroupInfo().isBaseType(TYPE_SPLIT)).isTrue() } @Test Loading @@ -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 Loading @@ -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) Loading @@ -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 { Loading @@ -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) } Loading @@ -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))) } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +16 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +135 −47 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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 {} Loading @@ -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; /** Loading @@ -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 */ Loading Loading @@ -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) { Loading @@ -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); } Loading @@ -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); } /** Loading @@ -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; } Loading @@ -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); Loading @@ -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) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +164 −38 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import org.mockito.Mockito.mock /** * Tests for [GroupedTaskInfo] * Build & Run: atest WMShellUnitTests:GroupedTaskInfoTest */ @SmallTest @RunWith(AndroidTestingRunner::class) Loading @@ -47,7 +48,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) assertThat(singleTaskGroupInfo().isBaseType(TYPE_FULLSCREEN)).isTrue() } @Test Loading @@ -66,7 +67,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSplitTasks_hasCorrectType() { assertThat(splitTasksGroupInfo().type).isEqualTo(TYPE_SPLIT) assertThat(splitTasksGroupInfo().isBaseType(TYPE_SPLIT)).isTrue() } @Test Loading @@ -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 Loading @@ -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) Loading @@ -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 { Loading @@ -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) } Loading @@ -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))) } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +16 −13 File changed.Preview size limit exceeded, changes collapsed. Show changes