From 7f47777d3a085e0abe976785b6baf6f76a1f083b Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Wed, 24 Jul 2024 17:43:44 +0800 Subject: Fix overlay below activity is dismissed When launching a new overlay, the overlay container below an activity could be dismissed unexpectedly because we check if the overlay container is the top TF of the task, but the top TF could also be occluded by fullscreen activity. This CL checks if the overlay is the top child of its parent container instead. Fixes: 339195872 Test: atest OverlayPresentationTest Flag: EXEMPT bugfix Change-Id: I25a31f64ff006895a97d09f7fb5a583bdec19f11 --- core/java/android/window/TaskFragmentInfo.java | 21 +++++++++++++++-- .../extensions/embedding/SplitController.java | 4 +--- .../embedding/TaskFragmentContainer.java | 7 ++++++ .../extensions/embedding/EmbeddingTestUtils.java | 24 ++++++++++++++++++- .../JetpackTaskFragmentOrganizerTest.java | 3 ++- .../embedding/OverlayPresentationTest.java | 27 +++++++++++----------- .../java/com/android/server/wm/TaskFragment.java | 16 ++++++++++++- 7 files changed, 81 insertions(+), 21 deletions(-) diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index fa5195727afe..23a1224fcc4e 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -102,6 +102,8 @@ public final class TaskFragmentInfo implements Parcelable { @NonNull private final Point mMinimumDimensions = new Point(); + private final boolean mIsTopNonFishingChild; + /** @hide */ public TaskFragmentInfo( @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token, @@ -110,7 +112,7 @@ public final class TaskFragmentInfo implements Parcelable { @NonNull List inRequestedTaskFragmentActivities, @NonNull Point positionInParent, boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip, boolean isClearedForReorderActivityToFront, - @NonNull Point minimumDimensions) { + @NonNull Point minimumDimensions, boolean isTopNonFinishingChild) { mFragmentToken = requireNonNull(fragmentToken); mToken = requireNonNull(token); mConfiguration.setTo(configuration); @@ -123,6 +125,7 @@ public final class TaskFragmentInfo implements Parcelable { mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip; mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront; mMinimumDimensions.set(minimumDimensions); + mIsTopNonFishingChild = isTopNonFinishingChild; } @NonNull @@ -211,6 +214,16 @@ public final class TaskFragmentInfo implements Parcelable { return mMinimumDimensions.y; } + /** + * Indicates that this TaskFragment is the top non-finishing child of its parent container + * among all Activities and TaskFragment siblings. + * + * @hide + */ + public boolean isTopNonFinishingChild() { + return mIsTopNonFishingChild; + } + /** * Returns {@code true} if the parameters that are important for task fragment organizers are * equal between this {@link TaskFragmentInfo} and {@param that}. @@ -236,7 +249,8 @@ public final class TaskFragmentInfo implements Parcelable { && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse && mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip && mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront - && mMinimumDimensions.equals(that.mMinimumDimensions); + && mMinimumDimensions.equals(that.mMinimumDimensions) + && mIsTopNonFishingChild == that.mIsTopNonFishingChild; } private TaskFragmentInfo(Parcel in) { @@ -252,6 +266,7 @@ public final class TaskFragmentInfo implements Parcelable { mIsTaskFragmentClearedForPip = in.readBoolean(); mIsClearedForReorderActivityToFront = in.readBoolean(); mMinimumDimensions.readFromParcel(in); + mIsTopNonFishingChild = in.readBoolean(); } /** @hide */ @@ -269,6 +284,7 @@ public final class TaskFragmentInfo implements Parcelable { dest.writeBoolean(mIsTaskFragmentClearedForPip); dest.writeBoolean(mIsClearedForReorderActivityToFront); mMinimumDimensions.writeToParcel(dest, flags); + dest.writeBoolean(mIsTopNonFishingChild); } @NonNull @@ -299,6 +315,7 @@ public final class TaskFragmentInfo implements Parcelable { + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip + " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront + " minimumDimensions=" + mMinimumDimensions + + " isTopNonFinishingChild=" + mIsTopNonFishingChild + "}"; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index e71d844ade59..409cde30cf8c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -2759,9 +2759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // in the same task, the overlay will be dismissed in case an activity above // the overlay is dismissed and the overlay is shown unexpectedly. for (final TaskFragmentContainer overlayContainer : overlayContainers) { - final boolean isTopNonFinishingOverlay = overlayContainer.equals( - overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer( - true /* includePin */, true /* includeOverlay */)); + final boolean isTopNonFinishingOverlay = overlayContainer.isTopNonFinishingChild(); final boolean areInSameTask = taskId == overlayContainer.getTaskId(); final boolean haveSameTag = overlayTag.equals(overlayContainer.getOverlayTag()); if (!associateLaunchingActivity && overlayContainer.isAlwaysOnTopOverlay() 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 7173b0c95230..d0e2c998e961 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -340,6 +340,13 @@ class TaskFragmentContainer { return mInfo != null && mInfo.isVisible(); } + /** + * See {@link TaskFragmentInfo#isTopNonFinishingChild()} + */ + boolean isTopNonFinishingChild() { + return mInfo != null && mInfo.isTopNonFinishingChild(); + } + /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/ boolean isInIntermediateState() { if (mInfo == null) { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index d649c6d57137..7dc78fdd601f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -163,12 +163,14 @@ public class EmbeddingTestUtils { } /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + @NonNull static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity) { return createMockTaskFragmentInfo(container, activity, true /* isVisible */); } /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + @NonNull static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity, boolean isVisible) { return new TaskFragmentInfo(container.getTaskFragmentToken(), @@ -182,7 +184,27 @@ public class EmbeddingTestUtils { false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, false /* isClearedForReorderActivityToFront */, - new Point()); + new Point(), + false /* isTopChild */); + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + @NonNull + static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity, boolean isVisible, boolean isOnTop) { + return new TaskFragmentInfo(container.getTaskFragmentToken(), + mock(WindowContainerToken.class), + new Configuration(), + 1, + isVisible, + Collections.singletonList(activity.getActivityToken()), + new ArrayList<>(), + new Point(), + false /* isTaskClearedForReuse */, + false /* isTaskFragmentClearedForPip */, + false /* isClearedForReorderActivityToFront */, + new Point(), + isOnTop); } static ActivityInfo createActivityInfoWithMinDimensions() { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index ad41b18dcbc6..8911d18b9b97 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -114,6 +114,7 @@ public class JetpackTaskFragmentOrganizerTest { mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, false /* isVisible */, new ArrayList<>(), new ArrayList<>(), new Point(), false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, - false /* isClearedForReorderActivityToFront */, new Point()); + false /* isClearedForReorderActivityToFront */, new Point(), + false /* isTopChild */); } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index f799bf9a1052..475475b05272 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -269,7 +269,7 @@ public class OverlayPresentationTest { } @Test - public void testCreateOrUpdateOverlay_visibleOverlayInTask_dismissOverlay() { + public void testCreateOrUpdateOverlay_topOverlayInTask_dismissOverlay() { createExistingOverlayContainers(); final TaskFragmentContainer overlayContainer = @@ -400,13 +400,13 @@ public class OverlayPresentationTest { } private void createExistingOverlayContainers() { - createExistingOverlayContainers(true /* visible */); + createExistingOverlayContainers(true /* isOnTop */); } - private void createExistingOverlayContainers(boolean visible) { - mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible, + private void createExistingOverlayContainers(boolean isOnTop) { + mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", isOnTop, true /* associatedLaunchingActivity */, mActivity); - mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible); + mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", isOnTop); List overlayContainers = mSplitController .getAllNonFinishingOverlayContainers(); assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2); @@ -1004,10 +1004,10 @@ public class OverlayPresentationTest { /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ @NonNull private TaskFragmentContainer createMockTaskFragmentContainer( - @NonNull Activity activity, boolean isVisible) { + @NonNull Activity activity, boolean isOnTop) { final TaskFragmentContainer container = createTfContainer(mSplitController, activity.getTaskId(), activity); - setupTaskFragmentInfo(container, activity, isVisible); + setupTaskFragmentInfo(container, activity, isOnTop); return container; } @@ -1019,8 +1019,8 @@ public class OverlayPresentationTest { @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag, - boolean isVisible) { - return createTestOverlayContainer(taskId, tag, isVisible, + boolean isOnTop) { + return createTestOverlayContainer(taskId, tag, isOnTop, true /* associateLaunchingActivity */); } @@ -1033,7 +1033,7 @@ public class OverlayPresentationTest { @NonNull private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag, - boolean isVisible, boolean associateLaunchingActivity, + boolean isOnTop, boolean associateLaunchingActivity, @Nullable Activity launchingActivity) { final Activity activity = launchingActivity != null ? launchingActivity : createMockActivity(); @@ -1044,14 +1044,15 @@ public class OverlayPresentationTest { .setLaunchOptions(Bundle.EMPTY) .setAssociatedActivity(associateLaunchingActivity ? activity : null) .build(); - setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible); + setupTaskFragmentInfo(overlayContainer, createMockActivity(), isOnTop); return overlayContainer; } private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity, - boolean isVisible) { - final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible); + boolean isOnTop) { + final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isOnTop, + isOnTop); container.setInfo(mTransaction, info); mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c83b28055a2f..ed0dc3be9465 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2819,7 +2819,21 @@ class TaskFragment extends WindowContainer { mClearedTaskForReuse, mClearedTaskFragmentForPip, mClearedForReorderActivityToFront, - calculateMinDimension()); + calculateMinDimension(), + isTopNonFinishingChild()); + } + + private boolean isTopNonFinishingChild() { + final WindowContainer parent = getParent(); + if (parent == null) { + // Either the TaskFragment is not attached or is going to destroy. Return false. + return false; + } + final ActivityRecord topNonFishingActivity = parent.getActivity(ar -> !ar.finishing); + // If the parent's top non-finishing activity is this TaskFragment's, it means + // this TaskFragment is the top non-finishing container of its parent. + return topNonFishingActivity != null && topNonFishingActivity + .equals(getTopNonFinishingActivity()); } /** -- cgit v1.2.3-59-g8ed1b