Merge "Notify organizer when the last activity in TaskFragment enter PiP" into tm-dev am: 88671a050f am: 38f42547e4
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17713423
Change-Id: I3439bb17ed2f7438133f2bbf01036761a0c7bfaf
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index a118f9a..f72164e 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -52,9 +52,6 @@
@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 @@
*/
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 boolean isEmpty() {
- return mIsEmpty;
+ return mRunningActivityCount == 0;
}
public boolean hasRunningActivity() {
@@ -140,6 +143,11 @@
return mIsTaskClearedForReuse;
}
+ /** @hide */
+ public boolean isTaskFragmentClearedForPip() {
+ return mIsTaskFragmentClearedForPip;
+ }
+
@WindowingMode
public int getWindowingMode() {
return mConfiguration.windowConfiguration.getWindowingMode();
@@ -156,25 +164,25 @@
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 @@
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 @@
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 3ec8843..33a41ec 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 @@
// 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 3cb9a56..1e1cbaf 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 @@
// 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;
if (singleActivity) {
@@ -2042,6 +2043,14 @@
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
@@ -2102,6 +2111,13 @@
// 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();
try {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 83bd979..4e0d84c 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 @@
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 @@
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 @@
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 @@
}
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 30c2413..54fa4e4 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 @@
}
@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 @@
}
@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);