From e6fc6a3c2a086c6d02a842e68993e2c4351f07bd Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Thu, 15 Oct 2020 11:41:50 +0800 Subject: [PATCH] Allow children task of created-by-organizer task to be organized Allows to organize direct child of root task created by organizer and extends TaskInfo to reveal the parent task id. To organize child task properly for each features, passes all child task organizer events to the listener listening to its parent task if feasible. Fix: 169267298 Fix: 169266482 Test: atest WindowOrganizerTests Test: atest SplitScreenTests Test: atest PinnedStackTests Test: atest WindowInsetPolicyTests Test: atest WMShellUnitTests Test: split screen works Change-Id: I8e66f810c86fcdac3287adfa208d8e34786fc9b5 --- api/test-current.txt | 1 + core/java/android/app/TaskInfo.java | 18 ++++++ .../android/wm/shell/ShellTaskOrganizer.java | 6 ++ .../splitscreen/SplitScreenController.java | 2 +- .../splitscreen/SplitScreenTaskListener.java | 56 ++++++++++++++++++- .../wm/shell/ShellTaskOrganizerTests.java | 14 +++++ .../core/java/com/android/server/wm/Task.java | 18 +++++- .../server/wm/WindowOrganizerTests.java | 15 +++++ 8 files changed, 125 insertions(+), 5 deletions(-) diff --git a/api/test-current.txt b/api/test-current.txt index 82838ea605ff2..a1b89590ab40f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -286,6 +286,7 @@ package android.app { public class TaskInfo { method @NonNull public android.content.res.Configuration getConfiguration(); method @NonNull public android.window.WindowContainerToken getToken(); + method public boolean hasParentTask(); } public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener { diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 5caf3057c8409..338973ff825e9 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -16,6 +16,8 @@ package android.app; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -203,6 +205,13 @@ public class TaskInfo { */ public ArrayList launchCookies = new ArrayList<>(); + /** + * The identifier of the parent task that is created by organizer, otherwise + * {@link ActivityTaskManager#INVALID_TASK_ID}. + * @hide + */ + public int parentTaskId; + TaskInfo() { // Do nothing } @@ -245,6 +254,12 @@ public class TaskInfo { launchCookies.add(cookie); } + /** @hide */ + @TestApi + public boolean hasParentTask() { + return parentTaskId != INVALID_TASK_ID; + } + /** * Reads the TaskInfo from a parcel. */ @@ -275,6 +290,7 @@ public class TaskInfo { source.readBinderList(launchCookies); letterboxActivityBounds = source.readTypedObject(Rect.CREATOR); positionInParent = source.readTypedObject(Point.CREATOR); + parentTaskId = source.readInt(); } /** @@ -308,6 +324,7 @@ public class TaskInfo { dest.writeBinderList(launchCookies); dest.writeTypedObject(letterboxActivityBounds, flags); dest.writeTypedObject(positionInParent, flags); + dest.writeInt(parentTaskId); } @Override @@ -330,6 +347,7 @@ public class TaskInfo { + " launchCookies" + launchCookies + " letterboxActivityBounds=" + letterboxActivityBounds + " positionInParent=" + positionInParent + + " parentTaskId: " + parentTaskId + "}"; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index d4ff275d426dd..51ddb17daa009 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -335,6 +335,12 @@ public class ShellTaskOrganizer extends TaskOrganizer { listener = mTaskListeners.get(taskId); if (listener != null) return listener; + // Next priority goes to the listener listening to its parent. + if (runningTaskInfo.hasParentTask()) { + listener = mTaskListeners.get(runningTaskInfo.parentTaskId); + if (listener != null) return listener; + } + // Next we try type specific listeners. final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo); return mTaskListeners.get(taskListenerType); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 69d428a3ae1fe..8b616e8fd1eec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -117,7 +117,7 @@ public class SplitScreenController implements SplitScreen, mTransactionPool = transactionPool; mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); mTaskOrganizer = shellTaskOrganizer; - mSplits = new SplitScreenTaskListener(this, shellTaskOrganizer); + mSplits = new SplitScreenTaskListener(this, shellTaskOrganizer, syncQueue); mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler, shellTaskOrganizer); mRotationController = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java index 191a317452e35..f709fed78b442 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java @@ -27,8 +27,10 @@ import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager.RunningTaskInfo; +import android.graphics.Point; import android.graphics.Rect; import android.util.Log; +import android.util.SparseArray; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -36,6 +38,8 @@ import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.Transitions; +import com.android.wm.shell.common.SyncTransactionQueue; import java.io.PrintWriter; @@ -44,6 +48,8 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final boolean DEBUG = SplitScreenController.DEBUG; private final ShellTaskOrganizer mTaskOrganizer; + private final SyncTransactionQueue mSyncQueue; + private final SparseArray mLeashByTaskId = new SparseArray<>(); RunningTaskInfo mPrimary; RunningTaskInfo mSecondary; @@ -58,9 +64,11 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { final SurfaceSession mSurfaceSession = new SurfaceSession(); SplitScreenTaskListener(SplitScreenController splitScreenController, - ShellTaskOrganizer shellTaskOrganizer) { + ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue) { mSplitScreenController = splitScreenController; mTaskOrganizer = shellTaskOrganizer; + mSyncQueue = syncQueue; } void init() { @@ -93,6 +101,11 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (this) { + if (taskInfo.hasParentTask()) { + handleChildTaskAppeared(taskInfo, leash); + return; + } + final int winMode = getWindowingMode(taskInfo); if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { ProtoLog.v(WM_SHELL_TASK_ORG, @@ -139,6 +152,11 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskVanished(RunningTaskInfo taskInfo) { synchronized (this) { + if (taskInfo.hasParentTask()) { + mLeashByTaskId.remove(taskInfo.taskId); + return; + } + final boolean isPrimaryTask = mPrimary != null && taskInfo.token.equals(mPrimary.token); final boolean isSecondaryTask = mSecondary != null @@ -165,7 +183,41 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { if (taskInfo.displayId != DEFAULT_DISPLAY) { return; } - mSplitScreenController.post(() -> handleTaskInfoChanged(taskInfo)); + synchronized (this) { + if (taskInfo.hasParentTask()) { + handleChildTaskChanged(taskInfo); + return; + } + + mSplitScreenController.post(() -> handleTaskInfoChanged(taskInfo)); + } + } + + private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + mLeashByTaskId.put(taskInfo.taskId, leash); + updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); + } + + private void handleChildTaskChanged(RunningTaskInfo taskInfo) { + final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); + } + + private void updateChildTaskSurface( + RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) { + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Point taskPositionInParent = taskInfo.positionInParent; + final Rect corp = new Rect(taskBounds); + corp.offset(-taskBounds.left, -taskBounds.top); + mSyncQueue.runInSync(t -> { + t.setWindowCrop(leash, corp); + t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); + if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } + }); } /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 35a2293cbf130..e4155a257ceea 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -247,6 +247,20 @@ public class ShellTaskOrganizerTests { assertTrue(gotException); } + @Test + public void testGetParentTaskListener() { + RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); + TrackingTaskListener mwListener = new TrackingTaskListener(); + mOrganizer.onTaskAppeared(task1, null); + mOrganizer.addListenerForTaskId(mwListener, task1.taskId); + RunningTaskInfo task2 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); + task2.parentTaskId = task1.taskId; + + mOrganizer.onTaskAppeared(task2, null); + + assertTrue(mwListener.appeared.contains(task2)); + } + @Test public void testTaskInfoToTaskListenerType_whenLetterboxBoundsPassed_returnsLetterboxType() { RunningTaskInfo taskInfo = createTaskInfo( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ef1a3be70f9db..9e8eb30b92d38 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -125,7 +125,6 @@ import static com.android.server.wm.Task.ActivityState.PAUSED; import static com.android.server.wm.Task.ActivityState.PAUSING; import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.Task.ActivityState.STARTED; -import static com.android.server.wm.Task.ActivityState.STOPPED; import static com.android.server.wm.Task.ActivityState.STOPPING; import static com.android.server.wm.TaskProto.ACTIVITY_TYPE; import static com.android.server.wm.TaskProto.BOUNDS; @@ -4113,6 +4112,10 @@ class Task extends WindowContainer { forAllActivities(r -> { info.addLaunchCookie(r.mLaunchCookie); }); + final Task rootTask = getRootTask(); + info.parentTaskId = rootTask == getParent() && rootTask.mCreatedByOrganizer + ? rootTask.mTaskId + : INVALID_TASK_ID; } @Nullable PictureInPictureParams getPictureInPictureParams() { @@ -4829,6 +4832,17 @@ class Task extends WindowContainer { return mTaskOrganizer != null; } + private boolean canBeOrganized() { + // All root tasks can be organized + if (isRootTask()) { + return true; + } + + // Task could be organized if it's the direct child of the root created by organizer. + final Task rootTask = getRootTask(); + return rootTask == getParent() && rootTask.mCreatedByOrganizer; + } + @Override boolean showSurfaceOnCreation() { // Organized tasks handle their own surface visibility @@ -4977,7 +4991,7 @@ class Task extends WindowContainer { // is created. return false; } - if (!isRootTask()) { + if (!canBeOrganized()) { return setTaskOrganizer(null); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index b4480aea3ce48..f1d49d5fc6c2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -976,6 +976,21 @@ public class WindowOrganizerTests extends WindowTestsBase { }); } + @Test + public void testReparentToOrganizedTask() { + final ITaskOrganizer organizer = registerMockOrganizer(); + Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( + mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + final Task task1 = createStack(); + final Task task2 = createTask(rootTask, false /* fakeDraw */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reparent(task1.mRemoteToken.toWindowContainerToken(), + rootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */); + mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); + assertTrue(task1.isOrganized()); + assertTrue(task2.isOrganized()); + } + /** * Verifies that task vanished is called for a specific task. */