diff options
7 files changed, 282 insertions, 57 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 836a6f610bbd..7cf3bafe499a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -41,10 +41,13 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Manages the recent task list from the system, caching it as necessary. @@ -62,6 +65,13 @@ public class RecentTasksController implements TaskStackListenerCallback, // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); + /** + * Maps taskId to {@link StagedSplitBounds} for both taskIDs. + * Meaning there will be two taskId integers mapping to the same object. + * If there's any ordering to the pairing than we can probably just get away with only one + * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now. + */ + private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not @@ -97,15 +107,20 @@ public class RecentTasksController implements TaskStackListenerCallback, /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ - public void addSplitPair(int taskId1, int taskId2) { + public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) { if (taskId1 == taskId2) { return; } // Remove any previous pairs removeSplitPair(taskId1); removeSplitPair(taskId2); + mTaskSplitBoundsMap.remove(taskId1); + mTaskSplitBoundsMap.remove(taskId2); + mSplitTasks.put(taskId1, taskId2); mSplitTasks.put(taskId2, taskId1); + mTaskSplitBoundsMap.put(taskId1, splitBounds); + mTaskSplitBoundsMap.put(taskId2, splitBounds); } /** @@ -116,6 +131,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { mSplitTasks.delete(taskId); mSplitTasks.delete(pairedTaskId); + mTaskSplitBoundsMap.remove(taskId); + mTaskSplitBoundsMap.remove(pairedTaskId); } } @@ -203,7 +220,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo)); + recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, + mTaskSplitBoundsMap.get(pairedTaskId))); } else { recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3c35e6a69bf5..95886c8f3deb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -96,6 +96,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -696,11 +697,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } mRecentTasks.ifPresent(recentTasks -> { + Rect topLeftBounds = mSplitLayout.getBounds1(); + Rect bottomRightBounds = mSplitLayout.getBounds2(); int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId(); + boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + int leftTopTaskId; + int rightBottomTaskId; + if (sideStageTopLeft) { + leftTopTaskId = sideStageTopTaskId; + rightBottomTaskId = mainStageTopTaskId; + } else { + leftTopTaskId = mainStageTopTaskId; + rightBottomTaskId = sideStageTopTaskId; + } + StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds, + leftTopTaskId, rightBottomTaskId); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks - recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId); + recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java index 0331ba19defe..603d05d78fc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -30,25 +30,34 @@ import androidx.annotation.Nullable; public class GroupedRecentTaskInfo implements Parcelable { public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; + public @Nullable StagedSplitBounds mStagedSplitBounds; public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { - this(task1, null); + this(task1, null, null); } public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, - @Nullable ActivityManager.RecentTaskInfo task2) { + @Nullable ActivityManager.RecentTaskInfo task2, + @Nullable StagedSplitBounds stagedSplitBounds) { mTaskInfo1 = task1; mTaskInfo2 = task2; + mStagedSplitBounds = stagedSplitBounds; } GroupedRecentTaskInfo(Parcel parcel) { mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR); } @Override public String toString() { - return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); + String taskString = "Task1: " + getTaskInfo(mTaskInfo1) + + ", Task2: " + getTaskInfo(mTaskInfo2); + if (mStagedSplitBounds != null) { + taskString += ", SplitBounds: " + mStagedSplitBounds.toString(); + } + return taskString; } private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { @@ -67,6 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedObject(mTaskInfo1, flags); parcel.writeTypedObject(mTaskInfo2, flags); + parcel.writeTypedObject(mStagedSplitBounds, flags); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java new file mode 100644 index 000000000000..aadf792c572f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.util; + +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container of various information needed to display split screen + * tasks/leashes/etc in Launcher + */ +public class StagedSplitBounds implements Parcelable { + public final Rect leftTopBounds; + public final Rect rightBottomBounds; + /** This rect represents the actual gap between the two apps */ + public final Rect visualDividerBounds; + // This class is orientation-agnostic, so we compute both for later use + public final float topTaskPercent; + public final float leftTaskPercent; + /** + * If {@code true}, that means at the time of creation of this object, the + * split-screened apps were vertically stacked. This is useful in scenarios like + * rotation where the bounds won't change, but this variable can indicate what orientation + * the bounds were originally in + */ + public final boolean appsStackedVertically; + public final int leftTopTaskId; + public final int rightBottomTaskId; + + public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, + int leftTopTaskId, int rightBottomTaskId) { + this.leftTopBounds = leftTopBounds; + this.rightBottomBounds = rightBottomBounds; + this.leftTopTaskId = leftTopTaskId; + this.rightBottomTaskId = rightBottomTaskId; + + if (rightBottomBounds.top > leftTopBounds.top) { + // vertical apps, horizontal divider + this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, + leftTopBounds.right, rightBottomBounds.top); + appsStackedVertically = true; + } else { + // horizontal apps, vertical divider + this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, + rightBottomBounds.left, leftTopBounds.bottom); + appsStackedVertically = false; + } + + leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right; + topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom; + } + + public StagedSplitBounds(Parcel parcel) { + leftTopBounds = parcel.readTypedObject(Rect.CREATOR); + rightBottomBounds = parcel.readTypedObject(Rect.CREATOR); + visualDividerBounds = parcel.readTypedObject(Rect.CREATOR); + topTaskPercent = parcel.readFloat(); + leftTaskPercent = parcel.readFloat(); + appsStackedVertically = parcel.readBoolean(); + leftTopTaskId = parcel.readInt(); + rightBottomTaskId = parcel.readInt(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedObject(leftTopBounds, flags); + parcel.writeTypedObject(rightBottomBounds, flags); + parcel.writeTypedObject(visualDividerBounds, flags); + parcel.writeFloat(topTaskPercent); + parcel.writeFloat(leftTaskPercent); + parcel.writeBoolean(appsStackedVertically); + parcel.writeInt(leftTopTaskId); + parcel.writeInt(rightBottomTaskId); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" + + "Divider: " + visualDividerBounds + "\n" + + "AppsVertical? " + appsStackedVertically; + } + + public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() { + @Override + public StagedSplitBounds createFromParcel(Parcel in) { + return new StagedSplitBounds(in); + } + + @Override + public StagedSplitBounds[] newArray(int size) { + return new StagedSplitBounds[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index a1e12319ac70..19a5417aace6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -31,10 +33,9 @@ import static org.mockito.Mockito.verify; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; -import android.app.WindowConfiguration; import android.content.Context; +import android.graphics.Rect; import android.view.SurfaceControl; -import android.window.TaskAppearedInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -45,6 +46,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import org.junit.Before; import org.junit.Test; @@ -106,8 +108,11 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - mRecentTasksController.addSplitPair(t2.taskId, t4.taskId); - mRecentTasksController.addSplitPair(t3.taskId, t5.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4); + StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5); + + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -126,7 +131,8 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3); // Add a pair - mRecentTasksController.addSplitPair(t2.taskId, t3.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3); + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); // Remove one of the tasks and ensure the pair is removed @@ -201,10 +207,23 @@ public class RecentTasksControllerTest extends ShellTestCase { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { GroupedRecentTaskInfo pair = recentTasks.get(i); - flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId; + int taskId1 = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null ? pair.mTaskInfo2.taskId : -1; + + if (pair.mTaskInfo2 != null) { + assertNotNull(pair.mStagedSplitBounds); + int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId; + int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId; + // Unclear if pairs are ordered by split position, most likely not. + assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); + assertTrue(bottomRightTaskId == taskId1 + || bottomRightTaskId == pair.mTaskInfo2.taskId); + } else { + assertNull(pair.mStagedSplitBounds); + } } assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + " Received: " + Arrays.toString(flattenedTaskIds), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java new file mode 100644 index 000000000000..ad73c56950bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java @@ -0,0 +1,94 @@ +package com.android.wm.shell.recents; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.util.StagedSplitBounds; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StagedSplitBoundsTest { + private static final int DEVICE_WIDTH = 100; + private static final int DEVICE_LENGTH = 200; + private static final int DIVIDER_SIZE = 20; + private static final int TASK_ID_1 = 4; + private static final int TASK_ID_2 = 9; + + // Bounds in screen space + private final Rect mTopRect = new Rect(); + private final Rect mBottomRect = new Rect(); + private final Rect mLeftRect = new Rect(); + private final Rect mRightRect = new Rect(); + + @Before + public void setup() { + mTopRect.set(0, 0, DEVICE_WIDTH, DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2); + mBottomRect.set(0, DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, + DEVICE_WIDTH, DEVICE_LENGTH); + mLeftRect.set(0, 0, DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, DEVICE_LENGTH); + mRightRect.set(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, 0, + DEVICE_WIDTH, DEVICE_LENGTH); + } + + @Test + public void testVerticalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + assertTrue(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + assertFalse(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(0, dividerBounds.left); + assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); + assertEquals(DEVICE_WIDTH, dividerBounds.right); + assertEquals(DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, dividerBounds.bottom); + } + + @Test + public void testVerticalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); + assertEquals(0, dividerBounds.top); + assertEquals(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, dividerBounds.right); + assertEquals(DEVICE_LENGTH, dividerBounds.bottom); + } + + @Test + public void testEqualVerticalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; + assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); + } + + @Test + public void testEqualHorizontalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; + assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java deleted file mode 100644 index 323b20e41a5c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.model; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * A group task in the recent tasks list. - * TODO: Move this into Launcher - */ -public class GroupTask { - public @NonNull Task task1; - public @Nullable Task task2; - - public GroupTask(@NonNull Task t1, @Nullable Task t2) { - task1 = t1; - task2 = t2; - } - - public GroupTask(@NonNull GroupTask group) { - task1 = new Task(group.task1); - task2 = group.task2 != null - ? new Task(group.task2) - : null; - } - - public boolean containsTask(int taskId) { - return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); - } -} |