From 840218d5df60855261b9c214f0edce5cfe19d2b4 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Tue, 20 Sep 2022 17:39:41 +0800 Subject: [PATCH] Allow ActivityEmbedding to finish Activity through WCT Before, we call Activity#finish() to finish activities when removing TaskFragment. This may start a CLOSE transition before the organizer has a chance to request the actual transition type. Now, we allow the organizer to finish activities through WCT so that the operation is atomic and the organizer can request the correct transition type. Bug: 240519866 Test: atest WmTests:TaskFragmentOrganizerControllerTest Test: atest CtsWindowManagerDeviceTestCases:TaskFragmentOrganizerTest Change-Id: I54671fb2dd34dca952468305429a90d89953de69 --- core/api/test-current.txt | 1 + .../window/WindowContainerTransaction.java | 20 +++++++++++++++++++ .../embedding/TaskFragmentContainer.java | 4 ++-- .../embedding/SplitControllerTest.java | 8 ++++---- .../embedding/TaskFragmentContainerTest.java | 19 +++++++++--------- .../server/wm/WindowOrganizerController.java | 18 +++++++++++++++++ .../TaskFragmentOrganizerControllerTest.java | 10 ++++++++++ 7 files changed, 64 insertions(+), 16 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f5ee4672b5609..93c0c4d7a0242 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3406,6 +3406,7 @@ package android.window { method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams); method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken); method public int describeContents(); + method @NonNull public android.window.WindowContainerTransaction finishActivity(@NonNull android.os.IBinder); method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken); method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean); diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index ffbdf08e99dc2..cfad1afe1b5b5 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -705,6 +705,23 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + /** + * Finishes the Activity. + * Comparing to directly calling {@link android.app.Activity#finish()}, calling this can make + * sure the finishing happens in the same transaction with other operations. + * @param activityToken activity to be finished. + */ + @NonNull + public WindowContainerTransaction finishActivity(@NonNull IBinder activityToken) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY) + .setContainer(activityToken) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + /** * Sets/removes the always on top flag for this {@code windowContainer}. See * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. @@ -1163,6 +1180,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18; public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19; public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20; + public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1484,6 +1502,8 @@ public final class WindowContainerTransaction implements Parcelable { + " alwaysOnTop=" + mAlwaysOnTop + "}"; case HIERARCHY_OP_TYPE_REMOVE_TASK: return "{RemoveTask: task=" + mContainer + "}"; + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: + return "{finishActivity: activity=" + mContainer + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 626e0d990a6df..18712aed1be64 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -432,7 +432,7 @@ class TaskFragmentContainer { // In case we have requested to reparent the activity to another container (as // pendingAppeared), we don't want to finish it with this container. && mController.getContainerWithActivity(activity) == this) { - activity.finish(); + wct.finishActivity(activity.getActivityToken()); } } @@ -457,7 +457,7 @@ class TaskFragmentContainer { || controller.shouldRetainAssociatedActivity(this, activity)) { continue; } - activity.finish(); + wct.finishActivity(activity.getActivityToken()); } mActivitiesToFinishOnExit.clear(); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 179696a063b12..25d0347562652 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -207,7 +207,7 @@ public class SplitControllerTest { verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); verify(mSplitController).removeContainer(tf); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); } @Test @@ -1004,9 +1004,9 @@ public class SplitControllerTest { assertTrue(primaryContainer.isFinished()); assertTrue(secondaryContainer0.isFinished()); assertTrue(secondaryContainer1.isFinished()); - verify(mActivity).finish(); - verify(secondaryActivity0).finish(); - verify(secondaryActivity1).finish(); + verify(mTransaction).finishActivity(mActivity.getActivityToken()); + verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); + verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); assertTrue(taskContainer.mContainers.isEmpty()); assertTrue(taskContainer.mSplitContainers.isEmpty()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 73428a2dc8004..35415d816d8bb 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -107,30 +107,29 @@ public class TaskFragmentContainerTest { final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); doReturn(container).when(mController).getContainerWithActivity(mActivity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); // Only remove the activity, but not clear the reference until appeared. - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity).finish(); + verify(mTransaction).finishActivity(mActivity.getActivityToken()); verify(mPresenter, never()).deleteTaskFragment(any(), any()); verify(mController, never()).removeContainer(any()); // Calling twice should not finish activity again. - clearInvocations(mActivity); - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + clearInvocations(mTransaction); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); verify(mPresenter, never()).deleteTaskFragment(any(), any()); verify(mController, never()).removeContainer(any()); // Remove all references after the container has appeared in server. doReturn(new ArrayList<>()).when(mInfo).getActivities(); container.setInfo(mTransaction, mInfo); - container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + container.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); - verify(mActivity, never()).finish(); - verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken()); + verify(mTransaction, never()).finishActivity(any()); + verify(mPresenter).deleteTaskFragment(mTransaction, container.getTaskFragmentToken()); verify(mController).removeContainer(container); } @@ -150,7 +149,7 @@ public class TaskFragmentContainerTest { // The activity is requested to be reparented, so don't finish it. container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); - verify(mActivity, never()).finish(); + verify(mTransaction, never()).finishActivity(any()); verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken()); verify(mController).removeContainer(container0); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 9456f0fcced7b..2e1477ddf0f16 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -25,6 +25,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER; @@ -1007,6 +1008,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub isInLockTaskMode); break; } + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: { + final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + if (activity == null || activity.finishing) { + break; + } + if (activity.isVisible()) { + // Prevent the transition from being executed too early if the activity is + // visible. + activity.finishIfPossible("finish-activity-op", false /* oomAdj */); + } else { + activity.destroyIfPossible("finish-activity-op"); + } + break; + } case HIERARCHY_OP_TYPE_LAUNCH_TASK: { mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "launchTask HierarchyOp"); @@ -1620,6 +1635,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub organizer); } break; + case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: + // Allow finish activity if it has the activity token. + break; default: // Other types of hierarchy changes are not allowed. String msg = "Permission Denial: " + func + " from pid=" diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 61cf8cc76d83b..1404de2534762 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -729,6 +729,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activity0, mDisplayContent.mFocusedApp); } + @Test + public void testApplyTransaction_finishActivity() { + final ActivityRecord activity = createActivityRecord(mDisplayContent); + + mTransaction.finishActivity(activity.token); + assertApplyTransactionAllowed(mTransaction); + + assertTrue(activity.finishing); + } + @Test public void testApplyTransaction_skipTransactionForUnregisterOrganizer() { mController.unregisterOrganizer(mIOrganizer);