Merge "Pass back split bounds and meta info with GroupedTaskInfo" into sc-v2-dev

This commit is contained in:
Vinit Nayak
2021-11-10 16:33:49 +00:00
committed by Android (Google) Code Review
7 changed files with 282 additions and 57 deletions

View File

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

View File

@@ -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;
@@ -691,11 +692,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);
}
});
}

View File

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

View File

@@ -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];
}
};
}

View File

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

View File

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

View File

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