diff options
13 files changed, 381 insertions, 95 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index e7c6445df154..5c90d0597d03 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5785,6 +5785,15 @@ package android.window { field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2 } + public final class TaskAppearedInfo implements android.os.Parcelable { + ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); + method public int describeContents(); + method @NonNull public android.view.SurfaceControl getLeash(); + method @NonNull public android.app.ActivityManager.RunningTaskInfo getTaskInfo(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR; + } + public class TaskOrganizer extends android.window.WindowOrganizer { ctor public TaskOrganizer(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int); @@ -5796,10 +5805,10 @@ package android.window { method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer(); + method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer(); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void unregisterOrganizer(); + method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void unregisterOrganizer(); } public final class WindowContainerToken implements android.os.Parcelable { diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 66007e5c17d6..75302293088f 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -165,7 +165,8 @@ interface IActivityTaskManager { int getTaskForActivity(in IBinder token, in boolean onlyRoot); /** Finish all activities that were started for result from the specified activity. */ void finishSubActivity(in IBinder token, in String resultWho, int requestCode); - ParceledListSlice getRecentTasks(int maxNum, int flags, int userId); + ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, + int userId); boolean willActivityBeVisible(in IBinder token); void setRequestedOrientation(in IBinder token, int requestedOrientation); int getRequestedOrientation(in IBinder token); diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index 12b16ff6645c..3a84c1f98ce6 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -17,7 +17,9 @@ package android.window; import android.app.ActivityManager; +import android.content.pm.ParceledListSlice; import android.window.ITaskOrganizer; +import android.window.TaskAppearedInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -26,8 +28,11 @@ interface ITaskOrganizerController { /** * Register a TaskOrganizer to manage all the tasks with supported windowing modes. + * + * @return a list of the tasks that should be managed by the organizer, not including tasks + * created via {@link #createRootTask}. */ - void registerTaskOrganizer(ITaskOrganizer organizer); + ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer); /** * Unregisters a previously registered task organizer. diff --git a/core/java/android/window/TaskAppearedInfo.aidl b/core/java/android/window/TaskAppearedInfo.aidl new file mode 100644 index 000000000000..13eba25f37a3 --- /dev/null +++ b/core/java/android/window/TaskAppearedInfo.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 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 android.window; + +/** + * Data object for the task info provided when a task is presented to an organizer. + * @hide + */ +parcelable TaskAppearedInfo; + diff --git a/core/java/android/window/TaskAppearedInfo.java b/core/java/android/window/TaskAppearedInfo.java new file mode 100644 index 000000000000..2ff331eb22e5 --- /dev/null +++ b/core/java/android/window/TaskAppearedInfo.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 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 android.window; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.app.ActivityManager.RunningTaskInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; + +/** + * Data object for the task info provided when a task is presented to an organizer. + * @hide + */ +@TestApi +public final class TaskAppearedInfo implements Parcelable { + + @NonNull + private final RunningTaskInfo mTaskInfo; + + @NonNull + private final SurfaceControl mLeash; + + @NonNull + public static final Creator<TaskAppearedInfo> CREATOR = new Creator<TaskAppearedInfo>() { + @Override + public TaskAppearedInfo createFromParcel(Parcel source) { + final RunningTaskInfo taskInfo = source.readTypedObject(RunningTaskInfo.CREATOR); + final SurfaceControl leash = source.readTypedObject(SurfaceControl.CREATOR); + return new TaskAppearedInfo(taskInfo, leash); + } + + @Override + public TaskAppearedInfo[] newArray(int size) { + return new TaskAppearedInfo[size]; + } + + }; + + public TaskAppearedInfo(@NonNull RunningTaskInfo taskInfo, @NonNull SurfaceControl leash) { + mTaskInfo = taskInfo; + mLeash = leash; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mTaskInfo, flags); + dest.writeTypedObject(mLeash, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * @return the task info. + */ + @NonNull + public RunningTaskInfo getTaskInfo() { + return mTaskInfo; + } + + /** + * @return the leash for the task. + */ + @NonNull + public SurfaceControl getLeash() { + return mLeash; + } +} diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index a7cb642b83f9..909bb47bf1a5 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -17,6 +17,7 @@ package android.window; import android.annotation.BinderThread; +import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -51,11 +52,16 @@ public class TaskOrganizer extends WindowOrganizer { /** * Register a TaskOrganizer to manage tasks as they enter a supported windowing mode. + * + * @return a list of the tasks that should be managed by the organizer, not including tasks + * created via {@link #createRootTask}. */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public final void registerOrganizer() { + @CallSuper + @NonNull + public List<TaskAppearedInfo> registerOrganizer() { try { - mTaskOrganizerController.registerTaskOrganizer(mInterface); + return mTaskOrganizerController.registerTaskOrganizer(mInterface).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -63,7 +69,8 @@ public class TaskOrganizer extends WindowOrganizer { /** Unregisters a previously registered task organizer. */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public final void unregisterOrganizer() { + @CallSuper + public void unregisterOrganizer() { try { mTaskOrganizerController.unregisterTaskOrganizer(mInterface); } catch (RemoteException e) { diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index bcef154beeb3..44744bc227a9 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -7,6 +7,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, + "-1683614271": { + "message": "Existing task: id=%d component=%s", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" + }, "-1534364071": { "message": "onTransitionReady %s: %s", "level": "VERBOSE", @@ -61,6 +67,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java" }, + "580605218": { + "message": "Registering organizer", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" + }, "980952660": { "message": "Task root back pressed taskId=%d", "level": "VERBOSE", 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 7ce65fd63334..d87de5a06c55 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -28,12 +28,14 @@ import android.annotation.IntDef; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; import android.util.Log; -import android.util.Pair; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.ITaskOrganizerController; +import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; @@ -42,6 +44,7 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.Arrays; +import java.util.List; /** * Unified task organizer for all components in the shell. @@ -82,7 +85,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { // Keeps track of all the tasks reported to this organizer (changes in windowing mode will // require us to report to both old and new listeners) - private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>(); + private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>(); // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks private final Transitions mTransitions; @@ -102,6 +105,19 @@ public class ShellTaskOrganizer extends TaskOrganizer { if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); } + @Override + public List<TaskAppearedInfo> registerOrganizer() { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Registering organizer"); + final List<TaskAppearedInfo> taskInfos = super.registerOrganizer(); + for (int i = 0; i < taskInfos.size(); i++) { + final TaskAppearedInfo info = taskInfos.get(i); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s", + info.getTaskInfo().taskId, info.getTaskInfo().baseIntent); + onTaskAppeared(info.getTaskInfo(), info.getLeash()); + } + return taskInfos; + } + /** * Adds a listener for tasks with given types. */ @@ -117,10 +133,11 @@ public class ShellTaskOrganizer extends TaskOrganizer { // Notify the listener of all existing tasks with the given type. for (int i = mTasks.size() - 1; i >= 0; i--) { - Pair<RunningTaskInfo, SurfaceControl> data = mTasks.valueAt(i); - final @TaskListenerType int taskListenerType = getTaskListenerType(data.first); + TaskAppearedInfo data = mTasks.valueAt(i); + final @TaskListenerType int taskListenerType = getTaskListenerType( + data.getTaskInfo()); if (taskListenerType == listenerType) { - listener.onTaskAppeared(data.first, data.second); + listener.onTaskAppeared(data.getTaskInfo(), data.getLeash()); } } } @@ -143,7 +160,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task appeared taskId=%d", taskInfo.taskId); - mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, leash)); + mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, leash)); final TaskListener listener = mTaskListenersByType.get(getTaskListenerType(taskInfo)); if (listener != null) { listener.onTaskAppeared(taskInfo, leash); @@ -154,10 +171,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { public void onTaskInfoChanged(RunningTaskInfo taskInfo) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId); - final Pair<RunningTaskInfo, SurfaceControl> data = mTasks.get(taskInfo.taskId); + final TaskAppearedInfo data = mTasks.get(taskInfo.taskId); final @TaskListenerType int listenerType = getTaskListenerType(taskInfo); - final @TaskListenerType int prevListenerType = getTaskListenerType(data.first); - mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, data.second)); + final @TaskListenerType int prevListenerType = getTaskListenerType(data.getTaskInfo()); + mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash())); if (prevListenerType != listenerType) { // TODO: We currently send vanished/appeared as the task moves between types, but // we should consider adding a different mode-changed callback @@ -167,7 +184,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { } listener = mTaskListenersByType.get(listenerType); if (listener != null) { - SurfaceControl leash = data.second; + SurfaceControl leash = data.getLeash(); listener.onTaskAppeared(taskInfo, leash); } } else { @@ -193,7 +210,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId); final @TaskListenerType int prevListenerType = - getTaskListenerType(mTasks.get(taskInfo.taskId).first); + getTaskListenerType(mTasks.get(taskInfo.taskId).getTaskInfo()); mTasks.remove(taskInfo.taskId); final TaskListener listener = mTaskListenersByType.get(prevListenerType); if (listener != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index a0ce9dabffe6..f3dadfcb933a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -26,7 +26,7 @@ import com.android.internal.protolog.common.IProtoLogGroup; public enum ShellProtoLogGroup implements IProtoLogGroup { // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict // with those in the framework ProtoLogGroup - WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), 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 f01fc517d5ba..5418a5b21680 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 @@ -22,6 +22,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -29,10 +33,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; +import android.content.pm.ParceledListSlice; import android.os.RemoteException; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; +import android.window.TaskAppearedInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -93,8 +99,12 @@ public class ShellTaskOrganizerTests { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue, - mTransactionPool, mTestExecutor, mTestExecutor); + try { + doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList()) + .when(mTaskOrganizerController).registerTaskOrganizer(any()); + } catch (RemoteException e) {} + mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue, + mTransactionPool, mTestExecutor, mTestExecutor)); } @Test @@ -116,8 +126,29 @@ public class ShellTaskOrganizerTests { } @Test + public void testRegisterWithExistingTasks() throws RemoteException { + // Setup some tasks + RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); + RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW); + ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>(); + taskInfos.add(new TaskAppearedInfo(task1, new SurfaceControl())); + taskInfos.add(new TaskAppearedInfo(task2, new SurfaceControl())); + doReturn(new ParceledListSlice(taskInfos)) + .when(mTaskOrganizerController).registerTaskOrganizer(any()); + + // Register and expect the tasks to be stored + mOrganizer.registerOrganizer(); + + // Check that the tasks are next reported when the listener is added + TrackingTaskListener listener = new TrackingTaskListener(); + mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW); + assertTrue(listener.appeared.contains(task1)); + assertTrue(listener.appeared.contains(task2)); + } + + @Test public void testAppearedVanished() { - RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW); + RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); TrackingTaskListener listener = new TrackingTaskListener(); mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW); mOrganizer.onTaskAppeared(taskInfo, null); @@ -129,7 +160,7 @@ public class ShellTaskOrganizerTests { @Test public void testAddListenerExistingTasks() { - RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW); + RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); mOrganizer.onTaskAppeared(taskInfo, null); TrackingTaskListener listener = new TrackingTaskListener(); @@ -139,7 +170,7 @@ public class ShellTaskOrganizerTests { @Test public void testWindowingModeChange() { - RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW); + RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW); TrackingTaskListener mwListener = new TrackingTaskListener(); TrackingTaskListener pipListener = new TrackingTaskListener(); mOrganizer.addListener(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW); @@ -148,14 +179,15 @@ public class ShellTaskOrganizerTests { assertTrue(mwListener.appeared.contains(taskInfo)); assertTrue(pipListener.appeared.isEmpty()); - taskInfo = createTaskInfo(WINDOWING_MODE_PINNED); + taskInfo = createTaskInfo(1, WINDOWING_MODE_PINNED); mOrganizer.onTaskInfoChanged(taskInfo); assertTrue(mwListener.vanished.contains(taskInfo)); assertTrue(pipListener.appeared.contains(taskInfo)); } - private RunningTaskInfo createTaskInfo(int windowingMode) { + private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); return taskInfo; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index e9dd85d50f8e..a66a2c4803cb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4773,9 +4773,11 @@ class Task extends WindowContainer<WindowContainer> { // If the task is not yet visible when it is added to the task organizer, then we should // hide it to allow the task organizer to show it when it is properly reparented. We // skip this for tasks created by the organizer because they can synchronously update - // the leash before new children are added to the task. + // the leash before new children are added to the task. Also skip this if the task + // has already been sent to the organizer which can happen before the first draw if + // an existing task is reported to the organizer when it first registers. if (!mAtmService.getTransitionController().isShellTransitionsEnabled() - && !mCreatedByOrganizer + && !mCreatedByOrganizer && !mTaskAppearedSent && mTaskOrganizer != null && !prevHasBeenVisible) { getSyncTransaction().hide(getSurfaceControl()); commitPendingTransaction(); @@ -4827,6 +4829,11 @@ class Task extends WindowContainer<WindowContainer> { @VisibleForTesting boolean setTaskOrganizer(ITaskOrganizer organizer) { + return setTaskOrganizer(organizer, false /* skipTaskAppeared */); + } + + @VisibleForTesting + boolean setTaskOrganizer(ITaskOrganizer organizer, boolean skipTaskAppeared) { if (mTaskOrganizer == organizer) { return false; } @@ -4839,7 +4846,9 @@ class Task extends WindowContainer<WindowContainer> { sendTaskVanished(prevOrganizer); if (mTaskOrganizer != null) { - sendTaskAppeared(); + if (!skipTaskAppeared) { + sendTaskAppeared(); + } } else { // No longer managed by any organizer. mTaskAppearedSent = false; @@ -4852,6 +4861,10 @@ class Task extends WindowContainer<WindowContainer> { return true; } + boolean updateTaskOrganizerState(boolean forceUpdate) { + return updateTaskOrganizerState(forceUpdate, false /* skipTaskAppeared */); + } + /** * Called when the task state changes (ie. from windowing mode change) an the task organizer * state should also be updated. @@ -4859,9 +4872,10 @@ class Task extends WindowContainer<WindowContainer> { * @param forceUpdate Updates the task organizer to the one currently specified in the task * org controller for the task's windowing mode, ignoring the cached * windowing mode checks. + * @param skipTaskAppeared Skips calling taskAppeared for the new organizer if it has changed * @return {@code true} if task organizer changed. */ - boolean updateTaskOrganizerState(boolean forceUpdate) { + boolean updateTaskOrganizerState(boolean forceUpdate, boolean skipTaskAppeared) { if (getSurfaceControl() == null) { // Can't call onTaskAppeared without a surfacecontrol, so defer this until after one // is created. @@ -4877,7 +4891,7 @@ class Task extends WindowContainer<WindowContainer> { if (!forceUpdate && mTaskOrganizer == organizer) { return false; } - return setTaskOrganizer(organizer); + return setTaskOrganizer(organizer, skipTaskAppeared); } @Override diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 8201d108c883..48550ed016c3 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -32,6 +32,7 @@ import android.app.ActivityManager.TaskDescription; import android.app.WindowConfiguration; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -39,6 +40,7 @@ import android.util.Slog; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; +import android.window.TaskAppearedInfo; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; @@ -76,8 +78,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { WINDOWING_MODE_FREEFORM }; - private final WindowManagerGlobalLock mGlobalLock; - private class DeathRecipient implements IBinder.DeathRecipient { ITaskOrganizer mTaskOrganizer; @@ -103,39 +103,38 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { * transaction before they are presented to the task org. */ private class TaskOrganizerCallbacks { - final WindowManagerService mService; final ITaskOrganizer mTaskOrganizer; final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer; - private final SurfaceControl.Transaction mTransaction; - - TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg, + TaskOrganizerCallbacks(ITaskOrganizer taskOrg, Consumer<Runnable> deferTaskOrgCallbacksConsumer) { - mService = wm; mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer; mTaskOrganizer = taskOrg; - mTransaction = wm.mTransactionFactory.get(); } IBinder getBinder() { return mTaskOrganizer.asBinder(); } + SurfaceControl prepareLeash(Task task, boolean visible, String reason) { + SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason); + if (!task.mCreatedByOrganizer && !visible) { + // To prevent flashes, we hide the task prior to sending the leash to the + // task org if the task has previously hidden (ie. when entering PIP) + mTransaction.hide(outSurfaceControl); + mTransaction.apply(); + } + return outSurfaceControl; + } + void onTaskAppeared(Task task) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task appeared taskId=%d", task.mTaskId); final boolean visible = task.isVisible(); final RunningTaskInfo taskInfo = task.getTaskInfo(); mDeferTaskOrgCallbacksConsumer.accept(() -> { try { - SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), - "TaskOrganizerController.onTaskAppeared"); - if (!task.mCreatedByOrganizer && !visible) { - // To prevent flashes, we hide the task prior to sending the leash to the - // task org if the task has previously hidden (ie. when entering PIP) - mTransaction.hide(outSurfaceControl); - mTransaction.apply(); - } - mTaskOrganizer.onTaskAppeared(taskInfo, outSurfaceControl); + mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task, visible, + "TaskOrganizerController.onTaskAppeared")); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onTaskAppeared callback", e); } @@ -208,8 +207,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mDeferTaskOrgCallbacksConsumer != null ? mDeferTaskOrgCallbacksConsumer : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable; - mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer, - deferTaskOrgCallbacksConsumer); + mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer); mDeathRecipient = new DeathRecipient(organizer); try { organizer.asBinder().linkToDeath(mDeathRecipient, 0); @@ -219,6 +217,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mUid = uid; } + /** + * Register this task with this state, but doesn't trigger the task appeared callback to + * the organizer. + */ + SurfaceControl addTaskWithoutCallback(Task t, String reason) { + t.mTaskAppearedSent = true; + if (!mOrganizedTasks.contains(t)) { + mOrganizedTasks.add(t); + } + return mOrganizer.prepareLeash(t, t.isVisible(), reason); + } + void addTask(Task t) { if (t.mTaskAppearedSent) return; @@ -265,6 +275,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + private final ActivityTaskManagerService mService; + private final WindowManagerGlobalLock mGlobalLock; + // List of task organizers by priority private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>(); private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>(); @@ -273,8 +286,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // Set of organized tasks (by taskId) that dispatch back pressed to their organizers private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet(); - private final ActivityTaskManagerService mService; - + private SurfaceControl.Transaction mTransaction; private RunningTaskInfo mTmpTaskInfo; private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer; @@ -299,7 +311,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode. */ @Override - public void registerTaskOrganizer(ITaskOrganizer organizer) { + public ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer) { enforceStackPermission("registerTaskOrganizer()"); final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); @@ -307,17 +319,36 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { synchronized (mGlobalLock) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d", organizer.asBinder(), uid); + + // Defer initializing the transaction since the transaction factory can be set up + // by the tests after construction of the controller + if (mTransaction == null) { + mTransaction = mService.mWindowManager.mTransactionFactory.get(); + } + if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) { mTaskOrganizers.add(organizer); mTaskOrganizerStates.put(organizer.asBinder(), new TaskOrganizerState(organizer, uid)); } + + final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>(); + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); mService.mRootWindowContainer.forAllTasks((task) -> { if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) { return; } - task.updateTaskOrganizerState(true /* forceUpdate */); + + boolean returnTask = !task.mCreatedByOrganizer; + task.updateTaskOrganizerState(true /* forceUpdate */, + returnTask /* skipTaskAppeared */); + if (returnTask) { + SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task, + "TaskOrganizerController.registerTaskOrganizer"); + taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl)); + } }); + return new ParceledListSlice<>(taskInfos); } } finally { Binder.restoreCallingIdentity(origId); 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 45534ddd57b5..aac83974eb51 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -54,12 +54,14 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.PictureInPictureParams; import android.content.pm.ActivityInfo; +import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -72,6 +74,7 @@ import android.view.Display; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; +import android.window.TaskAppearedInfo; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -79,8 +82,10 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; /** @@ -93,14 +98,27 @@ import java.util.List; @Presubmit @RunWith(WindowTestRunner.class) public class WindowOrganizerTests extends WindowTestsBase { - private ITaskOrganizer registerMockOrganizer() { + + private ITaskOrganizer createMockOrganizer() { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); when(organizer.asBinder()).thenReturn(new Binder()); + return organizer; + } - mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer); + private ITaskOrganizer registerMockOrganizer(ArrayList<TaskAppearedInfo> existingTasks) { + final ITaskOrganizer organizer = createMockOrganizer(); + ParceledListSlice<TaskAppearedInfo> tasks = + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer); + if (existingTasks != null) { + existingTasks.addAll(tasks.getList()); + } return organizer; } + private ITaskOrganizer registerMockOrganizer() { + return registerMockOrganizer(null); + } + Task createTask(Task stack, boolean fakeDraw) { final Task task = createTaskInStack(stack, 0); @@ -128,27 +146,21 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testAppearVanish() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack); - final ITaskOrganizer organizer = registerMockOrganizer(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - stack.setTaskOrganizer(organizer); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.removeImmediately(); verify(organizer).onTaskVanished(any()); } @Test public void testAppearWaitsForVisibility() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack, false); - final ITaskOrganizer organizer = registerMockOrganizer(); - - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - stack.setTaskOrganizer(organizer); verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); @@ -163,9 +175,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testNoVanishedIfNoAppear() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack, false /* hasBeenVisible */); - final ITaskOrganizer organizer = registerMockOrganizer(); // In this test we skip making the Task visible, and verify // that even though a TaskOrganizer is set remove doesn't emit @@ -179,28 +191,25 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testTaskNoDraw() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack, false /* fakeDraw */); - final ITaskOrganizer organizer = registerMockOrganizer(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); assertTrue(stack.isOrganized()); mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); - verify(organizer, never()).onTaskVanished(any()); + assertTaskVanished(organizer, false /* expectVanished */, stack); assertFalse(stack.isOrganized()); } @Test public void testClearOrganizer() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack); - final ITaskOrganizer organizer = registerMockOrganizer(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - stack.setTaskOrganizer(organizer); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); assertTrue(stack.isOrganized()); @@ -211,16 +220,15 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testUnregisterOrganizer() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack); - final ITaskOrganizer organizer = registerMockOrganizer(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); assertTrue(stack.isOrganized()); mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); - verify(organizer).onTaskVanished(any()); + assertTaskVanished(organizer, true /* expectVanished */, stack); assertFalse(stack.isOrganized()); } @@ -232,37 +240,47 @@ public class WindowOrganizerTests extends WindowTestsBase { final Task task2 = createTask(stack2); final Task stack3 = createStack(); final Task task3 = createTask(stack3); - final ITaskOrganizer organizer = registerMockOrganizer(); - - // verify that tasks are appeared on registration - verify(organizer, times(3)) - .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); + final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>(); + final ITaskOrganizer organizer = registerMockOrganizer(existingTasks); + + // verify that tasks are returned and taskAppeared is not called + assertContainsTasks(existingTasks, stack, stack2, stack3); + verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class), + any(SurfaceControl.class)); + verify(organizer, times(0)).onTaskVanished(any()); assertTrue(stack.isOrganized()); - // Now we replace the registration and1 verify the new organizer receives tasks - final ITaskOrganizer organizer2 = registerMockOrganizer(); - verify(organizer2, times(3)) - .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); + // Now we replace the registration and verify the new organizer receives existing tasks + final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>(); + final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2); + assertContainsTasks(existingTasks2, stack, stack2, stack3); + verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class), + any(SurfaceControl.class)); verify(organizer2, times(0)).onTaskVanished(any()); - // One for task - verify(organizer, times(3)).onTaskVanished(any()); + // Removed tasks from the original organizer + assertTaskVanished(organizer, true /* expectVanished */, stack, stack2, stack3); assertTrue(stack2.isOrganized()); // Now we unregister the second one, the first one should automatically be reregistered // so we verify that it's now seeing changes. mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); - verify(organizer, times(6)) + verify(organizer, times(3)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - verify(organizer2, times(3)).onTaskVanished(any()); + assertTaskVanished(organizer2, true /* expectVanished */, stack, stack2, stack3); } @Test public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException { final Task stack = createStack(); final Task task = createTask(stack); + final Task stack2 = createStack(); + final Task task2 = createTask(stack2); + ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>(); + final ITaskOrganizer organizer = registerMockOrganizer(existingTasks); + assertContainsTasks(existingTasks, stack, stack2); - final ITaskOrganizer organizer = registerMockOrganizer(); - verify(organizer, times(1)) + // Verify we don't get onTaskAppeared if we are returned the tasks + verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); } @@ -954,9 +972,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testPreventDuplicateAppear() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack, false /* fakeDraw */); - final ITaskOrganizer organizer = registerMockOrganizer(); stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); stack.setTaskOrganizer(organizer); @@ -977,17 +995,14 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testInterceptBackPressedOnTaskRoot() throws RemoteException { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stack = createStack(); final Task task = createTask(stack); final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task); final Task stack2 = createStack(); final Task task2 = createTask(stack2); final ActivityRecord activity2 = createActivityRecordInTask(stack.mDisplayContent, task2); - final ITaskOrganizer organizer = registerMockOrganizer(); - // Setup the task to be controlled by the MW mode organizer - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); assertTrue(stack.isOrganized()); assertTrue(stack2.isOrganized()); @@ -1014,9 +1029,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testBLASTCallbackWithMultipleWindows() throws Exception { + final ITaskOrganizer organizer = registerMockOrganizer(); final Task stackController = createStack(); final Task task = createTask(stackController); - final ITaskOrganizer organizer = registerMockOrganizer(); final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1"); final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2"); makeWindowVisible(w1); @@ -1067,4 +1082,37 @@ public class WindowOrganizerTests extends WindowTestsBase { assertFalse(daTask.isForceHidden()); }); } + + /** + * Verifies that task vanished is called for a specific task. + */ + private void assertTaskVanished(ITaskOrganizer organizer, boolean expectVanished, Task... tasks) + throws RemoteException { + ArgumentCaptor<RunningTaskInfo> arg = ArgumentCaptor.forClass(RunningTaskInfo.class); + verify(organizer, atLeastOnce()).onTaskVanished(arg.capture()); + List<RunningTaskInfo> taskInfos = arg.getAllValues(); + + HashSet<Integer> vanishedTaskIds = new HashSet<>(); + for (int i = 0; i < taskInfos.size(); i++) { + vanishedTaskIds.add(taskInfos.get(i).taskId); + } + HashSet<Integer> taskIds = new HashSet<>(); + for (int i = 0; i < tasks.length; i++) { + taskIds.add(tasks[i].mTaskId); + } + + assertTrue(expectVanished + ? vanishedTaskIds.containsAll(taskIds) + : !vanishedTaskIds.removeAll(taskIds)); + } + + private void assertContainsTasks(List<TaskAppearedInfo> taskInfos, Task... expectedTasks) { + HashSet<Integer> taskIds = new HashSet<>(); + for (int i = 0; i < taskInfos.size(); i++) { + taskIds.add(taskInfos.get(i).getTaskInfo().taskId); + } + for (int i = 0; i < expectedTasks.length; i++) { + assertTrue(taskIds.contains(expectedTasks[i].mTaskId)); + } + } } |