diff options
5 files changed, 95 insertions, 16 deletions
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index cefc8717ed01..3bda2e60334a 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -562,9 +562,6 @@ public class AppTransitionController { leafTask = null; break; } - // The activity may be a child of embedded Task, but we want to find the owner Task. - // As a result, find the organized TaskFragment first. - final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment(); // There are also cases where the Task contains non-embedded activity, such as launching // split TaskFragments from a non-embedded activity. // The hierarchy may looks like this: @@ -575,10 +572,9 @@ public class AppTransitionController { // - TaskFragment // - Activity // We also want to have the organizer handle the transition for such case. - final Task task = organizedTaskFragment != null - ? organizedTaskFragment.getTask() - : r.getTask(); - if (task == null) { + final Task task = r.getTask(); + // We don't support embedding in PiP, leave the animation to the PipTaskOrganizer. + if (task == null || task.inPinnedWindowingMode()) { leafTask = null; break; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0be29a9d0925..ca4c450a4592 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2097,11 +2097,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> r.setWindowingMode(intermediateWindowingMode); r.mWaitForEnteringPinnedMode = true; rootTask.forAllTaskFragments(tf -> { - // When the Task is entering picture-in-picture, we should clear all override from - // the client organizer, so the PIP activity can get the correct config from the - // Task, and prevent conflict with the PipTaskOrganizer. - if (tf.isOrganizedTaskFragment()) { - tf.resetAdjacentTaskFragment(); + if (!tf.isOrganizedTaskFragment()) { + return; + } + tf.resetAdjacentTaskFragment(); + if (tf.getTopNonFinishingActivity() != null) { + // When the Task is entering picture-in-picture, we should clear all override + // from the client organizer, so the PIP activity can get the correct config + // from the Task, and prevent conflict with the PipTaskOrganizer. tf.updateRequestedOverrideConfiguration(EMPTY); } }); @@ -2116,7 +2119,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // to the root pinned task r.supportsEnterPipOnTaskSwitch = false; - if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) { + if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip + && organizedTf.isTaskVisibleRequested()) { // Dispatch the pending info to TaskFragmentOrganizer before PIP animation. // Otherwise, it will keep waiting for the empty TaskFragment to be non-empty. mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent( diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index e0346544d528..c78d4cbd45f5 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2302,11 +2302,32 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mTaskFragmentOrganizer != null; } + /** Whether the Task should be visible. */ + boolean isTaskVisibleRequested() { + final Task task = getTask(); + return task != null && task.isVisibleRequested(); + } + boolean isReadyToTransit() { + // We only wait when this is organized to give the organizer a chance to update. + if (!isOrganizedTaskFragment()) { + return true; + } // We don't want to start the transition if the organized TaskFragment is empty, unless // it is requested to be removed. - return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null - || mIsRemovalRequested; + if (getTopNonFinishingActivity() != null || mIsRemovalRequested) { + return true; + } + // Organizer shouldn't change embedded TaskFragment in PiP. + if (isEmbeddedTaskFragmentInPip()) { + return true; + } + // The TaskFragment becomes empty because the last running activity enters PiP when the Task + // is minimized. + if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) { + return true; + } + return false; } /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ @@ -2424,8 +2445,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { mIsRemovalRequested = false; resetAdjacentTaskFragment(); cleanUp(); + final boolean shouldExecuteAppTransition = + mClearedTaskFragmentForPip && isTaskVisibleRequested(); super.removeImmediately(); sendTaskFragmentVanished(); + if (shouldExecuteAppTransition && mDisplayContent != null) { + // When the Task is still visible, and the TaskFragment is removed because the last + // running activity is reparenting to PiP, it is possible that no activity is getting + // paused or resumed (having an embedded activity in split), thus we need to relayout + // and execute it explicitly. + mAtmService.addWindowLayoutReasons( + ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED); + mDisplayContent.executeAppTransition(); + } } /** Called on remove to cleanup. */ diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index bd351acf5119..764960ad9176 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -518,8 +518,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr // longer has activities. As a result, the organizer will never get this info changed event // and will not delete the TaskFragment because the organizer thinks the TaskFragment still // has running activities. + // Another case is when an organized TaskFragment became empty because the last running + // activity is reparented to a new Task due to enter PiP. We also want to notify the + // organizer, so it can remove the empty TaskFragment and update the paired TaskFragment + // without causing the extra delay. return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED - && task.topRunningActivity() == null && lastInfo != null + && (task.topRunningActivity() == null || info.isTaskFragmentClearedForPip()) + && lastInfo != null && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 54fa4e4bf7ec..240943cfab71 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -19,8 +19,10 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -30,6 +32,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import android.content.res.Configuration; @@ -257,6 +260,9 @@ public class TaskFragmentTest extends WindowTestsBase { .createActivityCount(1) .build(); final ActivityRecord activity0 = taskFragment0.getTopMostActivity(); + final ActivityRecord activity1 = taskFragment1.getTopMostActivity(); + activity0.setVisibility(true /* visible */, false /* deferHidingClient */); + activity1.setVisibility(true /* visible */, false /* deferHidingClient */); spyOn(mAtm.mTaskFragmentOrganizerController); // Move activity to pinned. @@ -269,11 +275,47 @@ public class TaskFragmentTest extends WindowTestsBase { final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo(); assertTrue(info.isTaskFragmentClearedForPip()); assertTrue(info.isEmpty()); + + // Notify organizer because the Task is still visible. + assertTrue(task.isVisibleRequested()); verify(mAtm.mTaskFragmentOrganizerController) .dispatchPendingInfoChangedEvent(taskFragment0); } @Test + public void testIsReadyToTransit() { + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setCreateParentTask() + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .build(); + final Task task = taskFragment.getTask(); + + // Not ready when it is empty. + assertFalse(taskFragment.isReadyToTransit()); + + // Ready when it is not empty. + final ActivityRecord activity = createActivityRecord(mDisplayContent); + doNothing().when(activity).setDropInputMode(anyInt()); + activity.reparent(taskFragment, WindowContainer.POSITION_TOP); + assertTrue(taskFragment.isReadyToTransit()); + + // Ready when the Task is in PiP. + taskFragment.removeChild(activity); + task.setWindowingMode(WINDOWING_MODE_PINNED); + assertTrue(taskFragment.isReadyToTransit()); + + // Ready when the TaskFragment is empty because of PiP, and the Task is invisible. + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskFragment.mClearedTaskFragmentForPip = true; + assertTrue(taskFragment.isReadyToTransit()); + + // Not ready if the task is still visible when the TaskFragment becomes empty. + doReturn(true).when(task).isVisibleRequested(); + assertFalse(taskFragment.isReadyToTransit()); + } + + @Test public void testActivityHasOverlayOverUntrustedModeEmbedded() { final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); |