diff options
5 files changed, 101 insertions, 29 deletions
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index a118f9a8188f..f72164e1f53f 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -52,9 +52,6 @@ public final class TaskFragmentInfo implements Parcelable { @NonNull private final Configuration mConfiguration = new Configuration(); - /** Whether the TaskFragment contains any child Window Container. */ - private final boolean mIsEmpty; - /** The number of the running activities in the TaskFragment. */ private final int mRunningActivityCount; @@ -77,21 +74,27 @@ public final class TaskFragmentInfo implements Parcelable { */ private final boolean mIsTaskClearedForReuse; + /** + * Whether the last running activity in the TaskFragment was reparented to a different Task + * because it is entering PiP. + */ + private final boolean mIsTaskFragmentClearedForPip; + /** @hide */ public TaskFragmentInfo( @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token, - @NonNull Configuration configuration, boolean isEmpty, int runningActivityCount, + @NonNull Configuration configuration, int runningActivityCount, boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent, - boolean isTaskClearedForReuse) { + boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip) { mFragmentToken = requireNonNull(fragmentToken); mToken = requireNonNull(token); mConfiguration.setTo(configuration); - mIsEmpty = isEmpty; mRunningActivityCount = runningActivityCount; mIsVisible = isVisible; mActivities.addAll(activities); mPositionInParent = requireNonNull(positionInParent); mIsTaskClearedForReuse = isTaskClearedForReuse; + mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip; } @NonNull @@ -110,7 +113,7 @@ public final class TaskFragmentInfo implements Parcelable { } public boolean isEmpty() { - return mIsEmpty; + return mRunningActivityCount == 0; } public boolean hasRunningActivity() { @@ -140,6 +143,11 @@ public final class TaskFragmentInfo implements Parcelable { return mIsTaskClearedForReuse; } + /** @hide */ + public boolean isTaskFragmentClearedForPip() { + return mIsTaskFragmentClearedForPip; + } + @WindowingMode public int getWindowingMode() { return mConfiguration.windowConfiguration.getWindowingMode(); @@ -156,25 +164,25 @@ public final class TaskFragmentInfo implements Parcelable { return mFragmentToken.equals(that.mFragmentToken) && mToken.equals(that.mToken) - && mIsEmpty == that.mIsEmpty && mRunningActivityCount == that.mRunningActivityCount && mIsVisible == that.mIsVisible && getWindowingMode() == that.getWindowingMode() && mActivities.equals(that.mActivities) && mPositionInParent.equals(that.mPositionInParent) - && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse; + && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse + && mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip; } private TaskFragmentInfo(Parcel in) { mFragmentToken = in.readStrongBinder(); mToken = in.readTypedObject(WindowContainerToken.CREATOR); mConfiguration.readFromParcel(in); - mIsEmpty = in.readBoolean(); mRunningActivityCount = in.readInt(); mIsVisible = in.readBoolean(); in.readBinderList(mActivities); mPositionInParent = requireNonNull(in.readTypedObject(Point.CREATOR)); mIsTaskClearedForReuse = in.readBoolean(); + mIsTaskFragmentClearedForPip = in.readBoolean(); } /** @hide */ @@ -183,12 +191,12 @@ public final class TaskFragmentInfo implements Parcelable { dest.writeStrongBinder(mFragmentToken); dest.writeTypedObject(mToken, flags); mConfiguration.writeToParcel(dest, flags); - dest.writeBoolean(mIsEmpty); dest.writeInt(mRunningActivityCount); dest.writeBoolean(mIsVisible); dest.writeBinderList(mActivities); dest.writeTypedObject(mPositionInParent, flags); dest.writeBoolean(mIsTaskClearedForReuse); + dest.writeBoolean(mIsTaskFragmentClearedForPip); } @NonNull @@ -210,12 +218,12 @@ public final class TaskFragmentInfo implements Parcelable { return "TaskFragmentInfo{" + " fragmentToken=" + mFragmentToken + " token=" + mToken - + " isEmpty=" + mIsEmpty + " runningActivityCount=" + mRunningActivityCount + " isVisible=" + mIsVisible + " activities=" + mActivities + " positionInParent=" + mPositionInParent + " isTaskClearedForReuse=" + mIsTaskClearedForReuse + + " isTaskFragmentClearedForPip" + mIsTaskFragmentClearedForPip + "}"; } 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 3ec8843838fe..33a41ecd49fa 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -155,13 +155,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check if there are no running activities - consider the container empty if there are no // non-finishing activities left. if (!taskFragmentInfo.hasRunningActivity()) { - // TODO(b/225371112): Don't finish dependent if the last activity is moved to the PIP - // Task. - // Do not finish the dependents if this TaskFragment was cleared due to launching - // activity in the Task. - final boolean shouldFinishDependent = - !taskFragmentInfo.isTaskClearedForReuse(); - mPresenter.cleanupContainer(container, shouldFinishDependent); + if (taskFragmentInfo.isTaskFragmentClearedForPip()) { + // Do not finish the dependents if the last activity is reparented to PiP. + // Instead, the original split should be cleanup, and the dependent may be expanded + // to fullscreen. + cleanupForEnterPip(container); + mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + } else { + // Do not finish the dependents if this TaskFragment was cleared due to launching + // activity in the Task. + final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); + mPresenter.cleanupContainer(container, shouldFinishDependent); + } } else if (wasInPip && isInPip) { // No update until exit PIP. return; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d46061649676..6f69e0324b0b 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2006,6 +2006,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // of the activity entering PIP r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); + final TaskFragment organizedTf = r.getOrganizedTaskFragment(); // TODO: Does it make sense to only count non-finishing activities? final boolean singleActivity = task.getActivityCount() == 1; final Task rootTask; @@ -2043,6 +2044,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> task.clearLastRecentsAnimationTransaction(false /* forceRemoveOverlay */); } + // The organized TaskFragment is becoming empty because this activity is reparented + // to a new PIP Task. In this case, we should notify the organizer about why the + // TaskFragment becomes empty. + if (organizedTf != null && organizedTf.getNonFinishingActivityCount() == 1 + && organizedTf.getTopNonFinishingActivity() == r) { + organizedTf.mClearedTaskFragmentForPip = true; + } + // There are multiple activities in the task and moving the top activity should // reveal/leave the other activities in their original task. // On the other hand, ActivityRecord#onParentChanged takes care of setting the @@ -2107,6 +2116,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Reset the state that indicates it can enter PiP while pausing after we've moved it // to the root pinned task r.supportsEnterPipOnTaskSwitch = false; + + if (organizedTf != null && organizedTf.mClearedTaskFragmentForPip) { + // 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( + organizedTf); + } } finally { mService.continueWindowLayout(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 83bd979a8e2f..4e0d84c67d03 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -193,6 +193,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean mClearedTaskForReuse; /** + * The last running activity of the TaskFragment was reparented to a different Task because it + * is entering PiP. + */ + boolean mClearedTaskFragmentForPip; + + /** * When we are in the process of pausing an activity, before starting the * next one, this variable holds the activity that is currently being paused. * @@ -847,6 +853,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask()); } + int getNonFinishingActivityCount() { + final int[] runningActivityCount = new int[1]; + forAllActivities(a -> { + if (!a.finishing) { + runningActivityCount[0]++; + } + }); + return runningActivityCount[0]; + } + boolean isTopActivityFocusable() { final ActivityRecord r = topRunningActivity(); return r != null ? r.isFocusable() @@ -1709,6 +1725,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { void addChild(WindowContainer child, int index) { ActivityRecord r = topRunningActivity(); mClearedTaskForReuse = false; + mClearedTaskFragmentForPip = false; boolean isAddingActivity = child.asActivityRecord() != null; final Task task = isAddingActivity ? getTask() : null; @@ -2253,22 +2270,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final Point positionInParent = new Point(); getRelativePosition(positionInParent); - final int[] runningActivityCount = new int[1]; - forAllActivities(a -> { - if (!a.finishing) { - runningActivityCount[0]++; - } - }); return new TaskFragmentInfo( mFragmentToken, mRemoteToken.toWindowContainerToken(), getConfiguration(), - runningActivityCount[0] == 0, - runningActivityCount[0], + getNonFinishingActivityCount(), isVisible(), childActivities, positionInParent, - mClearedTaskForReuse); + mClearedTaskForReuse, + mClearedTaskFragmentForPip); } @Nullable 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 30c2413eb6f7..54fa4e4bf7ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -209,7 +209,7 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test - public void testEmbeddedTaskFragmentEnterPip_resetOrganizerOverrideConfig() { + public void testEmbeddedTaskFragmentEnterPip_singleActivity_resetOrganizerOverrideConfig() { final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) @@ -242,6 +242,38 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test + public void testEmbeddedTaskFragmentEnterPip_multiActivities_notifyOrganizer() { + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment0 = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .createActivityCount(1) + .build(); + final TaskFragment taskFragment1 = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .createActivityCount(1) + .build(); + final ActivityRecord activity0 = taskFragment0.getTopMostActivity(); + spyOn(mAtm.mTaskFragmentOrganizerController); + + // Move activity to pinned. + mRootWindowContainer.moveActivityToPinnedRootTask(activity0, + null /* launchIntoPipHostActivity */, "test"); + + // Ensure taskFragment requested config is reset. + assertTrue(taskFragment0.mClearedTaskFragmentForPip); + assertFalse(taskFragment1.mClearedTaskFragmentForPip); + final TaskFragmentInfo info = taskFragment0.getTaskFragmentInfo(); + assertTrue(info.isTaskFragmentClearedForPip()); + assertTrue(info.isEmpty()); + verify(mAtm.mTaskFragmentOrganizerController) + .dispatchPendingInfoChangedEvent(taskFragment0); + } + + @Test public void testActivityHasOverlayOverUntrustedModeEmbedded() { final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); |