Merge "Notify organizer when the last activity in TaskFragment enter PiP" into tm-dev

This commit is contained in:
Chris Li
2022-04-14 10:22:10 +00:00
committed by Android (Google) Code Review
5 changed files with 101 additions and 29 deletions

View File

@@ -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
+ "}";
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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.
@@ -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

View File

@@ -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())
@@ -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,