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. */