Loading core/java/android/window/TaskFragmentInfo.java +20 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -110,7 +113,7 @@ public final class TaskFragmentInfo implements Parcelable { } public boolean isEmpty() { return mIsEmpty; return mRunningActivityCount == 0; } public boolean hasRunningActivity() { Loading Loading @@ -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(); Loading @@ -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 */ Loading @@ -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 Loading @@ -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 + "}"; } Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +12 −7 Original line number Diff line number Diff line Loading @@ -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. 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(); final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); mPresenter.cleanupContainer(container, shouldFinishDependent); } } else if (wasInPip && isInPip) { // No update until exit PIP. return; Loading services/core/java/com/android/server/wm/RootWindowContainer.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); } Loading services/core/java/com/android/server/wm/TaskFragment.java +20 −9 Original line number Diff line number Diff line Loading @@ -192,6 +192,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. Loading Loading @@ -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() Loading Loading @@ -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; Loading Loading @@ -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 Loading services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +33 −1 Original line number Diff line number Diff line Loading @@ -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()) Loading Loading @@ -241,6 +241,38 @@ public class TaskFragmentTest extends WindowTestsBase { assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration()); } @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, Loading Loading
core/java/android/window/TaskFragmentInfo.java +20 −12 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -110,7 +113,7 @@ public final class TaskFragmentInfo implements Parcelable { } public boolean isEmpty() { return mIsEmpty; return mRunningActivityCount == 0; } public boolean hasRunningActivity() { Loading Loading @@ -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(); Loading @@ -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 */ Loading @@ -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 Loading @@ -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 + "}"; } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +12 −7 Original line number Diff line number Diff line Loading @@ -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. 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(); final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); mPresenter.cleanupContainer(container, shouldFinishDependent); } } else if (wasInPip && isInPip) { // No update until exit PIP. return; Loading
services/core/java/com/android/server/wm/RootWindowContainer.java +16 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); } Loading
services/core/java/com/android/server/wm/TaskFragment.java +20 −9 Original line number Diff line number Diff line Loading @@ -192,6 +192,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. Loading Loading @@ -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() Loading Loading @@ -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; Loading Loading @@ -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 Loading
services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +33 −1 Original line number Diff line number Diff line Loading @@ -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()) Loading Loading @@ -241,6 +241,38 @@ public class TaskFragmentTest extends WindowTestsBase { assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration()); } @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, Loading