From ffecd017112a09e64156c8c12a528de2f8aad3eb Mon Sep 17 00:00:00 2001 From: Chris Li Date: Wed, 7 Dec 2022 17:21:49 +0800 Subject: [PATCH] Allow launching placeholder when the TaskFragment is visible Before, we only launch placeholder from the top most TaskFragmentContainer. Now we also launch placeholder from the one that is not the top most, but is visible. This can happen when the top most is transparent. Bug: 261550242 Test: atest WMJetpackUnitTests:SplitControllerTest Merged-In: I6e3c4122d5d618b8a9ac054c19b0a3d19e7d3b20 Change-Id: I6e3c4122d5d618b8a9ac054c19b0a3d19e7d3b20 --- .../extensions/embedding/SplitController.java | 54 +++++++++++----- .../extensions/embedding/SplitPresenter.java | 8 +-- .../embedding/TaskFragmentContainer.java | 5 ++ .../embedding/EmbeddingTestUtils.java | 8 ++- .../embedding/SplitControllerTest.java | 62 +++++++++++++++++++ .../embedding/TaskFragmentContainerTest.java | 23 +++++++ .../com/android/server/wm/TaskFragment.java | 4 +- 7 files changed, 139 insertions(+), 25 deletions(-) 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 d3dc05fb92d4b..405ee14c23463 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -425,12 +425,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. removeContainer(container); - // Make sure the top container is updated. - final TaskFragmentContainer newTopContainer = getTopActiveContainer( - container.getTaskId()); - if (newTopContainer != null) { - updateContainer(wct, newTopContainer); - } + // Make sure the containers in the Task are up-to-date. + updateContainersInTaskIfVisible(wct, container.getTaskId()); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } @@ -470,6 +466,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTask(wct, taskContainer); } + void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer != null && taskContainer.isVisible()) { + updateContainersInTask(wct, taskContainer); + } + } + private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be @@ -1320,9 +1323,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen void removeContainer(@NonNull TaskFragmentContainer container) { // Remove all split containers that included this one final TaskContainer taskContainer = container.getTaskContainer(); - if (taskContainer == null) { - return; - } taskContainer.mContainers.remove(container); // Marked as a pending removal which will be removed after it is actually removed on the // server side (#onTaskFragmentVanished). @@ -1515,14 +1515,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskFragmentContainer container = getContainerWithActivity(activity); - // Don't launch placeholder if the container is occluded. - if (container != null && container != getTopActiveContainer(container.getTaskId())) { - return false; - } - - final SplitContainer splitContainer = getActiveSplitForContainer(container); - if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { - // Don't launch placeholder in primary split container + if (container != null && !allowLaunchPlaceholder(container)) { + // We don't allow activity in this TaskFragment to launch placeholder. return false; } @@ -1550,6 +1544,32 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } + /** Whether or not to allow activity in this container to launch placeholder. */ + @GuardedBy("mLock") + private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) { + final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId()); + if (container != topContainer) { + // The container is not the top most. + if (!container.isVisible()) { + // In case the container is visible (the one on top may be transparent), we may + // still want to launch placeholder even if it is not the top most. + return false; + } + if (topContainer.isWaitingActivityAppear()) { + // When the top container appeared info is not sent by the server yet, the visible + // check above may not be reliable. + return false; + } + } + + final SplitContainer splitContainer = getActiveSplitForContainer(container); + if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) { + // Don't launch placeholder for primary split container. + return false; + } + return true; + } + /** * Gets the activity options for starting the placeholder activity. In case the placeholder is * launched when the Task is in the background, we don't want to bring the Task to the front. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 47253d388f0dd..a432e2b10c142 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -154,12 +154,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void cleanupContainer(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { container.finish(shouldFinishDependent, this, wct, mController); - - final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer( - container.getTaskId()); - if (newTopContainer != null) { - mController.updateContainer(wct, newTopContainer); - } + // Make sure the containers in the Task is up-to-date. + mController.updateContainersInTaskIfVisible(wct, container.getTaskId()); } /** 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 fcf0ac78af387..8240874ca53d8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -184,6 +184,11 @@ class TaskFragmentContainer { return allActivities; } + /** Whether this TaskFragment is visible. */ + boolean isVisible() { + return mInfo != null && mInfo.isVisible(); + } + /** 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 bc03e4ec303df..2f92a577baa21 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,11 +163,17 @@ public class EmbeddingTestUtils { /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity) { + return createMockTaskFragmentInfo(container, activity, true /* isVisible */); + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity, boolean isVisible) { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 1, - true /* isVisible */, + isVisible, Collections.singletonList(activity.getActivityToken()), new Point(), false /* isTaskClearedForReuse */, 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 6725dfdb94e81..3cc31f9761c12 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 @@ -1254,6 +1254,68 @@ public class SplitControllerTest { verify(mEmbeddingCallback).accept(any()); } + @Test + public void testLaunchPlaceholderIfNecessary_nonEmbeddedActivity() { + // Launch placeholder for non embedded activity. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), + any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment() { + // Launch placeholder for activity in top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + assertTrue(container.hasActivity(mActivity.getActivityToken())); + verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), + any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment() { + // Do not launch placeholder for invisible activity below the top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, + TASK_ID); + bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, + false /* isVisible */)); + topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); + assertFalse(bottomTf.isVisible()); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any()); + } + + @Test + public void testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment() { + // Launch placeholder for visible activity below the top TaskFragment. + setupPlaceholderRule(mActivity); + mTransactionManager.startNewTransaction(); + final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, + TASK_ID); + bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, + true /* isVisible */)); + topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); + assertTrue(bottomTf.isVisible()); + mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, + true /* isOnCreated */); + + verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); 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 5c3ba72e23619..98772360a3211 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 @@ -500,6 +500,29 @@ public class TaskFragmentContainerTest { assertEquals(2, taskContainer.indexOf(tf1)); } + @Test + public void testIsVisible() { + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer container = new TaskFragmentContainer( + null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, + null /* pairedPrimaryTaskFragment */); + + // Not visible when there is not appeared. + assertFalse(container.isVisible()); + + // Respect info.isVisible. + TaskFragmentInfo info = createMockTaskFragmentInfo(container, mActivity, + true /* isVisible */); + container.setInfo(mTransaction, info); + + assertTrue(container.isVisible()); + + info = createMockTaskFragmentInfo(container, mActivity, false /* isVisible */); + container.setInfo(mTransaction, info); + + assertFalse(container.isVisible()); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { final Activity activity = mock(Activity.class); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index ae4f8947cc863..40ab68b011ef0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2552,7 +2552,7 @@ class TaskFragment extends WindowContainer { mRemoteToken.toWindowContainerToken(), getConfiguration(), getNonFinishingActivityCount(), - isVisibleRequested(), + shouldBeVisible(null /* starting */), childActivities, positionInParent, mClearedTaskForReuse, @@ -2848,6 +2848,8 @@ class TaskFragment extends WindowContainer { if (parentTf != null) { parentTf.onActivityVisibleRequestedChanged(); } + // Send the info changed to update the TaskFragment visibility. + sendTaskFragmentInfoChanged(); } @Nullable