Polish ActivityEmbedding enter/exit PiP (1/n)

1. When moveActivityToPinnedRootTask with creating a new Task for PiP,
   make sure the Task's initial bounds is the same as the activity
   parent TaskFragment so the animation starts from the correct bounds.
2. When exit PiP to previous Task, make sure we are animating the
   correct window surface. For the previous implementation. there can
   also be TRANSIT_CHANGE change for entering ActivityEmbedding split
   (from PiP) in the same transition.

Bug: 207070762
Test: atest WmTests:RootWindowContainerTests
Test: atest WmTests:TransitionTests
Change-Id: Ifba090ad9ac9fb7033d343eab1c87c1a67bb9c11
This commit is contained in:
Chris Li
2022-10-10 21:21:51 +08:00
parent db8b1f3ae8
commit b033b5a431
7 changed files with 105 additions and 9 deletions

View File

@@ -407,6 +407,7 @@ public final class TransitionInfo implements Parcelable {
public static final class Change implements Parcelable { public static final class Change implements Parcelable {
private final WindowContainerToken mContainer; private final WindowContainerToken mContainer;
private WindowContainerToken mParent; private WindowContainerToken mParent;
private WindowContainerToken mLastParent;
private final SurfaceControl mLeash; private final SurfaceControl mLeash;
private @TransitionMode int mMode = TRANSIT_NONE; private @TransitionMode int mMode = TRANSIT_NONE;
private @ChangeFlags int mFlags = FLAG_NONE; private @ChangeFlags int mFlags = FLAG_NONE;
@@ -435,6 +436,7 @@ public final class TransitionInfo implements Parcelable {
private Change(Parcel in) { private Change(Parcel in) {
mContainer = in.readTypedObject(WindowContainerToken.CREATOR); mContainer = in.readTypedObject(WindowContainerToken.CREATOR);
mParent = in.readTypedObject(WindowContainerToken.CREATOR); mParent = in.readTypedObject(WindowContainerToken.CREATOR);
mLastParent = in.readTypedObject(WindowContainerToken.CREATOR);
mLeash = new SurfaceControl(); mLeash = new SurfaceControl();
mLeash.readFromParcel(in); mLeash.readFromParcel(in);
mMode = in.readInt(); mMode = in.readInt();
@@ -458,6 +460,14 @@ public final class TransitionInfo implements Parcelable {
mParent = parent; mParent = parent;
} }
/**
* Sets the parent of this change's container before the transition if this change's
* container is reparented in the transition.
*/
public void setLastParent(@Nullable WindowContainerToken lastParent) {
mLastParent = lastParent;
}
/** Sets the transition mode for this change */ /** Sets the transition mode for this change */
public void setMode(@TransitionMode int mode) { public void setMode(@TransitionMode int mode) {
mMode = mode; mMode = mode;
@@ -541,6 +551,17 @@ public final class TransitionInfo implements Parcelable {
return mParent; return mParent;
} }
/**
* @return the parent of the changing container before the transition if it is reparented
* in the transition. The parent window may not be collected in the transition as a
* participant, and it may have been detached from the display. {@code null} if the changing
* container has not been reparented in the transition, or if the parent is not organizable.
*/
@Nullable
public WindowContainerToken getLastParent() {
return mLastParent;
}
/** @return which action this change represents. */ /** @return which action this change represents. */
public @TransitionMode int getMode() { public @TransitionMode int getMode() {
return mMode; return mMode;
@@ -640,6 +661,7 @@ public final class TransitionInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mContainer, flags); dest.writeTypedObject(mContainer, flags);
dest.writeTypedObject(mParent, flags); dest.writeTypedObject(mParent, flags);
dest.writeTypedObject(mLastParent, flags);
mLeash.writeToParcel(dest, flags); mLeash.writeToParcel(dest, flags);
dest.writeInt(mMode); dest.writeInt(mMode);
dest.writeInt(mFlags); dest.writeInt(mFlags);
@@ -685,6 +707,7 @@ public final class TransitionInfo implements Parcelable {
+ mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation
+ " endFixedRotation=" + mEndFixedRotation; + " endFixedRotation=" + mEndFixedRotation;
if (mSnapshot != null) out += " snapshot=" + mSnapshot; if (mSnapshot != null) out += " snapshot=" + mSnapshot;
if (mLastParent != null) out += " lastParent=" + mLastParent;
return out + "}"; return out + "}";
} }
} }

View File

@@ -452,14 +452,17 @@ public class PipTransition extends PipTransitionController {
@NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) { @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
TransitionInfo.Change pipChange = pipTaskChange; TransitionInfo.Change pipChange = pipTaskChange;
if (pipChange == null) { if (mCurrentPipTaskToken == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
} else if (pipChange == null) {
// The pipTaskChange is null, this can happen if we are reparenting the PIP activity // The pipTaskChange is null, this can happen if we are reparenting the PIP activity
// back to its original Task. In that case, we should animate the activity leash // back to its original Task. In that case, we should animate the activity leash
// instead, which should be the only non-task, independent, TRANSIT_CHANGE window. // instead, which should be the change whose last parent is the recorded PiP Task.
for (int i = info.getChanges().size() - 1; i >= 0; --i) { for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i); final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() == null && change.getMode() == TRANSIT_CHANGE if (mCurrentPipTaskToken.equals(change.getLastParent())) {
&& TransitionInfo.isIndependent(change, info)) { // Find the activity that is exiting PiP.
pipChange = change; pipChange = change;
break; break;
} }

View File

@@ -2020,7 +2020,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
// to its previous freeform bounds. // to its previous freeform bounds.
rootTask.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds); rootTask.setLastNonFullscreenBounds(task.mLastNonFullscreenBounds);
rootTask.setBounds(task.getBounds()); // When creating a new Task for PiP, set its initial bounds as the TaskFragment in
// case the activity is embedded, so that it can be animated to PiP window from the
// current bounds.
// Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode
// will be updated later after this is collected in transition.
rootTask.setBoundsUnchecked(r.getTaskFragment().getBounds());
// Move the last recents animation transaction from original task to the new one. // Move the last recents animation transaction from original task to the new one.
if (task.mLastRecentsAnimationTransaction != null) { if (task.mLastRecentsAnimationTransaction != null) {

View File

@@ -2605,6 +2605,13 @@ class Task extends TaskFragment {
return boundsChange; return boundsChange;
} }
/** Sets the requested bounds regardless of the windowing mode. */
int setBoundsUnchecked(@NonNull Rect bounds) {
final int boundsChange = super.setBounds(bounds);
updateSurfaceBounds();
return boundsChange;
}
@Override @Override
public boolean isCompatible(int windowingMode, int activityType) { public boolean isCompatible(int windowingMode, int activityType) {
// TODO: Should we just move this to ConfigurationContainer? // TODO: Should we just move this to ConfigurationContainer?
@@ -5849,10 +5856,7 @@ class Task extends TaskFragment {
return BOUNDS_CHANGE_NONE; return BOUNDS_CHANGE_NONE;
} }
final int result = super.setBounds(!inMultiWindowMode() ? null : bounds); return setBoundsUnchecked(!inMultiWindowMode() ? null : bounds);
updateSurfaceBounds();
return result;
} }
@Override @Override

View File

@@ -1561,6 +1561,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (info.mEndParent != null) { if (info.mEndParent != null) {
change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken()); change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
} }
if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
&& target.getParent() != info.mStartParent) {
change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
}
change.setMode(info.getTransitMode(target)); change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds); change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target)); change.setFlags(info.getChangeFlags(target));

View File

@@ -72,6 +72,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.UserHandle; import android.os.UserHandle;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -391,6 +392,33 @@ public class RootWindowContainerTests extends WindowTestsBase {
assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode()); assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode());
} }
@Test
public void testMovingEmbeddedActivityToPip() {
final Rect taskBounds = new Rect(0, 0, 800, 1000);
final Rect taskFragmentBounds = new Rect(0, 0, 400, 1000);
final Task task = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */);
task.setBounds(taskBounds);
assertEquals(taskBounds, task.getBounds());
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.createActivityCount(2)
.setBounds(taskFragmentBounds)
.build();
assertEquals(taskFragmentBounds, taskFragment.getBounds());
final ActivityRecord topActivity = taskFragment.getTopMostActivity();
// Move the top activity to pinned root task.
mRootWindowContainer.moveActivityToPinnedRootTask(topActivity,
null /* launchIntoPipHostActivity */, "test");
final Task pinnedRootTask = task.getDisplayArea().getRootPinnedTask();
// Ensure the initial bounds of the PiP Task is the same as the TaskFragment.
ensureTaskPlacement(pinnedRootTask, topActivity);
assertEquals(taskFragmentBounds, pinnedRootTask.getBounds());
}
private static void ensureTaskPlacement(Task task, ActivityRecord... activities) { private static void ensureTaskPlacement(Task task, ActivityRecord... activities) {
final ArrayList<ActivityRecord> taskActivities = new ArrayList<>(); final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();

View File

@@ -1324,6 +1324,35 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
} }
@Test
public void testReparentChangeLastParent() {
final Transition transition = createTestTransition(TRANSIT_CHANGE);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
// Reparent activity in transition.
final Task lastParent = createTask(mDisplayContent);
final Task newParent = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(lastParent);
activity.mVisibleRequested = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
changes.put(activity, new Transition.ChangeInfo(activity));
activity.reparent(newParent, POSITION_TOP);
activity.mVisibleRequested = false;
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, mMockT);
// Change contains last parent info.
assertEquals(1, info.getChanges().size());
assertEquals(lastParent.mRemoteToken.toWindowContainerToken(),
info.getChanges().get(0).getLastParent());
}
@Test @Test
public void testIncludeEmbeddedActivityReparent() { public void testIncludeEmbeddedActivityReparent() {
final Transition transition = createTestTransition(TRANSIT_OPEN); final Transition transition = createTestTransition(TRANSIT_OPEN);