Add support for multiple roots in a transition

Each display needs its own root, so transitions that span
multiple displays will require multiple roots and correspondence
between members and their associated displays.

Bug: 261418859
Test: TransitionTests ShellTransitionTests
Change-Id: I02022fe610a43eef6c03b5729e77a7fcbc4674ca
This commit is contained in:
Evan Rosky
2023-02-07 17:52:02 -08:00
parent bdf9aaf0e9
commit ee41c09e19
13 changed files with 388 additions and 123 deletions

View File

@@ -24,6 +24,7 @@ import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -58,6 +59,7 @@ import java.util.List;
* @hide
*/
public final class TransitionInfo implements Parcelable {
private static final String TAG = "TransitionInfo";
/**
* Modes are only a sub-set of all the transit-types since they are per-container
@@ -180,9 +182,7 @@ public final class TransitionInfo implements Parcelable {
private final @TransitionType int mType;
private final @TransitionFlags int mFlags;
private final ArrayList<Change> mChanges = new ArrayList<>();
private SurfaceControl mRootLeash;
private final Point mRootOffset = new Point();
private final ArrayList<Root> mRoots = new ArrayList<>();
private AnimationOptions mOptions;
@@ -196,10 +196,7 @@ public final class TransitionInfo implements Parcelable {
mType = in.readInt();
mFlags = in.readInt();
in.readTypedList(mChanges, Change.CREATOR);
mRootLeash = new SurfaceControl();
mRootLeash.readFromParcel(in);
mRootLeash.setUnreleasedWarningCallSite("TransitionInfo");
mRootOffset.readFromParcel(in);
in.readTypedList(mRoots, Root.CREATOR);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
}
@@ -209,8 +206,7 @@ public final class TransitionInfo implements Parcelable {
dest.writeInt(mType);
dest.writeInt(mFlags);
dest.writeTypedList(mChanges);
mRootLeash.writeToParcel(dest, flags);
mRootOffset.writeToParcel(dest, flags);
dest.writeTypedList(mRoots, flags);
dest.writeTypedObject(mOptions, flags);
}
@@ -234,10 +230,15 @@ public final class TransitionInfo implements Parcelable {
return 0;
}
/** @see #getRootLeash() */
public void setRootLeash(@NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
mRootLeash = leash;
mRootOffset.set(offsetLeft, offsetTop);
/** @see #getRoot */
public void addRootLeash(int displayId, @NonNull SurfaceControl leash,
int offsetLeft, int offsetTop) {
mRoots.add(new Root(displayId, leash, offsetLeft, offsetTop));
}
/** @see #getRoot */
public void addRoot(Root other) {
mRoots.add(other);
}
public void setAnimationOptions(AnimationOptions options) {
@@ -252,24 +253,53 @@ public final class TransitionInfo implements Parcelable {
return mFlags;
}
/**
* @return The number of animation roots. Most transitions should have 1, but there may be more
* in some cases (such as a transition spanning multiple displays).
*/
public int getRootCount() {
return mRoots.size();
}
/**
* @return the transition-root at a specific index.
*/
@NonNull
public Root getRoot(int idx) {
return mRoots.get(idx);
}
/**
* @return the index of the transition-root associated with `displayId` or -1 if not found.
*/
public int findRootIndex(int displayId) {
for (int i = 0; i < mRoots.size(); ++i) {
if (mRoots.get(i).mDisplayId == displayId) {
return i;
}
}
return -1;
}
/**
* @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
* participants to animate within. This will generally be placed at the highest-z-order
* shared ancestor of all participants. While this is non-null, it's possible for the rootleash
* to be invalid if the transition is a no-op.
*
* @deprecated Use {@link #getRoot} instead. This call assumes there is only one root.
*/
@Deprecated
@NonNull
public SurfaceControl getRootLeash() {
if (mRootLeash == null) {
throw new IllegalStateException("Trying to get a leash which wasn't set");
if (mRoots.isEmpty()) {
throw new IllegalStateException("Trying to get a root leash from a no-op transition.");
}
return mRootLeash;
}
/** @return the offset (relative to the screen) of the root leash. */
@NonNull
public Point getRootOffset() {
return mRootOffset;
if (mRoots.size() > 1) {
android.util.Log.e(TAG, "Assuming one animation root when there are more.",
new Throwable());
}
return mRoots.get(0).mLeash;
}
public AnimationOptions getAnimationOptions() {
@@ -316,8 +346,15 @@ public final class TransitionInfo implements Parcelable {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
+ " ro=" + mRootOffset + " c=[");
sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
.append(Integer.toHexString(mFlags)).append(" r=[");
for (int i = 0; i < mRoots.size(); ++i) {
if (i > 0) {
sb.append(',');
}
sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
}
sb.append("] c=[");
for (int i = 0; i < mChanges.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -441,8 +478,8 @@ public final class TransitionInfo implements Parcelable {
c.mSnapshot = null;
}
}
if (mRootLeash != null) {
mRootLeash.release();
for (int i = 0; i < mRoots.size(); ++i) {
mRoots.get(i).mLeash.release();
}
}
@@ -469,10 +506,11 @@ public final class TransitionInfo implements Parcelable {
for (int i = 0; i < mChanges.size(); ++i) {
out.mChanges.add(mChanges.get(i).localRemoteCopy());
}
out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
for (int i = 0; i < mRoots.size(); ++i) {
out.mRoots.add(mRoots.get(i).localRemoteCopy());
}
// Doesn't have any native stuff, so no need for actual copy
out.mOptions = mOptions;
out.mRootOffset.set(mRootOffset);
return out;
}
@@ -489,6 +527,8 @@ public final class TransitionInfo implements Parcelable {
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private boolean mAllowEnterPip;
private int mStartDisplayId = INVALID_DISPLAY;
private int mEndDisplayId = INVALID_DISPLAY;
private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
/**
@@ -519,6 +559,8 @@ public final class TransitionInfo implements Parcelable {
mEndRelOffset.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
mAllowEnterPip = in.readBoolean();
mStartDisplayId = in.readInt();
mEndDisplayId = in.readInt();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
mEndFixedRotation = in.readInt();
@@ -539,6 +581,8 @@ public final class TransitionInfo implements Parcelable {
out.mEndRelOffset.set(mEndRelOffset);
out.mTaskInfo = mTaskInfo;
out.mAllowEnterPip = mAllowEnterPip;
out.mStartDisplayId = mStartDisplayId;
out.mEndDisplayId = mEndDisplayId;
out.mStartRotation = mStartRotation;
out.mEndRotation = mEndRotation;
out.mEndFixedRotation = mEndFixedRotation;
@@ -600,6 +644,12 @@ public final class TransitionInfo implements Parcelable {
mAllowEnterPip = allowEnterPip;
}
/** Sets the start and end rotation of this container. */
public void setDisplayId(int start, int end) {
mStartDisplayId = start;
mEndDisplayId = end;
}
/** Sets the start and end rotation of this container. */
public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
mStartRotation = start;
@@ -718,6 +768,14 @@ public final class TransitionInfo implements Parcelable {
return mAllowEnterPip;
}
public int getStartDisplayId() {
return mStartDisplayId;
}
public int getEndDisplayId() {
return mEndDisplayId;
}
@Surface.Rotation
public int getStartRotation() {
return mStartRotation;
@@ -769,6 +827,8 @@ public final class TransitionInfo implements Parcelable {
mEndRelOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartDisplayId);
dest.writeInt(mEndDisplayId);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
dest.writeInt(mEndFixedRotation);
@@ -815,6 +875,11 @@ public final class TransitionInfo implements Parcelable {
if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
sb.append(" eo="); sb.append(mEndRelOffset);
}
sb.append(" d=");
if (mStartDisplayId != mEndDisplayId) {
sb.append(mStartDisplayId).append("->");
}
sb.append(mEndDisplayId);
if (mStartRotation != mEndRotation) {
sb.append(" r="); sb.append(mStartRotation);
sb.append("->"); sb.append(mEndRotation);
@@ -1101,4 +1166,86 @@ public final class TransitionInfo implements Parcelable {
};
}
}
/**
* An animation root in a transition. There is one of these for each display that contains
* participants. It will be placed, in z-order, right above the top-most participant and at the
* same position in the hierarchy. As a result, if all participants are animating within a
* part of the screen, the root-leash will only be in that part of the screen. In these cases,
* it's relative position (from the screen) is stored in {@link Root#getOffset}.
*/
public static final class Root implements Parcelable {
private final int mDisplayId;
private final SurfaceControl mLeash;
private final Point mOffset = new Point();
public Root(int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
mDisplayId = displayId;
mLeash = leash;
mOffset.set(offsetLeft, offsetTop);
}
private Root(Parcel in) {
mDisplayId = in.readInt();
mLeash = new SurfaceControl();
mLeash.readFromParcel(in);
mLeash.setUnreleasedWarningCallSite("TransitionInfo.Root");
mOffset.readFromParcel(in);
}
private Root localRemoteCopy() {
return new Root(mDisplayId, new SurfaceControl(mLeash, "localRemote"),
mOffset.x, mOffset.y);
}
/** @return the id of the display this root is on. */
public int getDisplayId() {
return mDisplayId;
}
/** @return the root's leash. Surfaces should be parented to this while animating. */
@NonNull
public SurfaceControl getLeash() {
return mLeash;
}
/** @return the offset (relative to its screen) of the root leash. */
@NonNull
public Point getOffset() {
return mOffset;
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mDisplayId);
mLeash.writeToParcel(dest, flags);
mOffset.writeToParcel(dest, flags);
}
@NonNull
public static final Creator<Root> CREATOR =
new Creator<Root>() {
@Override
public Root createFromParcel(Parcel in) {
return new Root(in);
}
@Override
public Root[] newArray(int size) {
return new Root[size];
}
};
/** @hide */
@Override
public int describeContents() {
return 0;
}
@Override
public String toString() {
return mDisplayId + "@" + mOffset + ":" + mLeash;
}
}
}

View File

@@ -489,10 +489,11 @@ public class PipTransition extends PipTransitionController {
// Reparent the pip leash to the root with max layer so that we can animate it outside of
// parent crop, and make sure it is not covered by other windows.
final SurfaceControl pipLeash = pipChange.getLeash();
startTransaction.reparent(pipLeash, info.getRootLeash());
final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
// Note: because of this, the bounds to animate should be translated to the root coordinate.
final Point offset = info.getRootOffset();
final Point offset = info.getRoot(rootIdx).getOffset();
final Rect currentBounds = mPipBoundsState.getBounds();
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);

View File

@@ -37,6 +37,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -128,6 +129,7 @@ class SplitScreenTransitions {
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -135,7 +137,7 @@ class SplitScreenTransitions {
t.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
t.reparent(leash, info.getRootLeash());
t.reparent(leash, info.getRoot(rootIdx).getLeash());
t.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -145,8 +147,9 @@ class SplitScreenTransitions {
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
final Rect endBounds = new Rect(change.getEndAbsBounds());
startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
final Point rootOffset = info.getRoot(rootIdx).getOffset();
startBounds.offset(-rootOffset.x, -rootOffset.y);
endBounds.offset(-rootOffset.x, -rootOffset.y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
boolean isRootOrSplitSideRoot = change.getParent() == null

View File

@@ -179,7 +179,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
out.getChanges().add(info.getChanges().get(i));
}
}
out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
for (int i = 0; i < info.getRootCount(); ++i) {
out.addRoot(info.getRoot(i));
}
out.setAnimationOptions(info.getAnimationOptions());
return out;
}

View File

@@ -383,9 +383,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
// No default animation for this, so just update bounds/position.
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
change.getEndAbsBounds().left - info.getRootOffset().x,
change.getEndAbsBounds().top - info.getRootOffset().y);
change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -474,8 +475,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
startTransaction, finishTransaction);
for (int i = 0; i < info.getRootCount(); ++i) {
addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
startTransaction, finishTransaction);
}
}
if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +523,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.

View File

@@ -75,6 +75,7 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Arrays;
@@ -415,8 +416,8 @@ public class Transitions implements RemoteCallable<Transitions> {
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
if (info.getRootLeash().isValid()) {
t.show(info.getRootLeash());
for (int i = 0; i < info.getRootCount(); ++i) {
t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
@@ -434,10 +435,12 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean hasParent = change.getParent() != null;
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (!hasParent) {
t.reparent(leash, info.getRootLeash());
t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
change.getStartAbsBounds().top - info.getRootOffset().y);
t.reparent(leash, info.getRoot(rootIdx).getLeash());
t.setPosition(leash,
change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
@@ -532,12 +535,6 @@ public class Transitions implements RemoteCallable<Transitions> {
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
if (!info.getRootLeash().isValid()) {
// Shell has some debug settings which makes calling binders with invalid
// surfaces crash, so replace it with a "real" one.
info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
.setContainerLayer().build(), 0, 0);
}
// Sleep starts a process of forcing all prior transitions to finish immediately
finishForSleep(null /* forceFinish */);
return;
@@ -546,10 +543,10 @@ public class Transitions implements RemoteCallable<Transitions> {
// Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
// screen-off, so there might no visibility change involved.
if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
// Invalid root-leash implies that the transition is empty/no-op, so just do
if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
// No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
transitionToken, info);
onAbort(active);
return;

View File

@@ -139,11 +139,12 @@ public class TransitionUtil {
// changes should be ordered top-to-bottom in z
final int mode = change.getMode();
t.reparent(leash, info.getRootLeash());
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
t.reparent(leash, info.getRoot(rootIdx).getLeash());
final Rect absBounds =
(mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
t.setPosition(leash, absBounds.left - info.getRootOffset().x,
absBounds.top - info.getRootOffset().y);
t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
absBounds.top - info.getRoot(rootIdx).getOffset().y);
// Put all the OPEN/SHOW on top
if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +180,13 @@ public class TransitionUtil {
// making leashes means we have to handle them specially.
return change.getLeash();
}
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName(change.getLeash().toString() + "_transition-leash")
.setContainerLayer()
// Initial the surface visible to respect the visibility of the original surface.
.setHidden(false)
.setParent(info.getRootLeash())
.setParent(info.getRoot(rootIdx).getLeash())
.build();
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +263,18 @@ public class TransitionUtil {
target.setRotationChange(change.getEndRotation() - change.getStartRotation());
return target;
}
/**
* Finds the "correct" root idx for a change. The change's end display is prioritized, then
* the start display. If there is no display, it will fallback on the 0th root in the
* transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
*/
public static int rootIndexFor(@NonNull TransitionInfo.Change change,
@NonNull TransitionInfo info) {
int rootIdx = info.findRootIndex(change.getEndDisplayId());
if (rootIdx >= 0) return rootIdx;
rootIdx = info.findRootIndex(change.getStartDisplayId());
if (rootIdx >= 0) return rootIdx;
return 0;
}
}

View File

@@ -30,6 +30,7 @@ import android.window.TransitionInfo;
*/
public class TransitionInfoBuilder {
final TransitionInfo mInfo;
static final int DISPLAY_ID = 0;
public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
this(type, 0 /* flags */);
@@ -38,7 +39,7 @@ public class TransitionInfoBuilder {
public TransitionInfoBuilder(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
mInfo = new TransitionInfo(type, flags);
mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +62,7 @@ public class TransitionInfoBuilder {
}
public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
mInfo.addChange(change);
return this;
}

View File

@@ -59,12 +59,14 @@ class RemoteTransitionAdapter {
// changes should be ordered top-to-bottom in z
val mode = change.mode
var rootIdx = info.findRootIndex(change.endDisplayId)
if (rootIdx < 0) rootIdx = 0
// Launcher animates leaf tasks directly, so always reparent all task leashes to root.
t.reparent(leash, info.rootLeash)
t.reparent(leash, info.getRoot(rootIdx).leash)
t.setPosition(
leash,
(change.startAbsBounds.left - info.rootOffset.x).toFloat(),
(change.startAbsBounds.top - info.rootOffset.y).toFloat()
(change.startAbsBounds.left - info.getRoot(rootIdx).offset.x).toFloat(),
(change.startAbsBounds.top - info.getRoot(rootIdx).offset.y).toFloat()
)
t.show(leash)
// Put all the OPEN/SHOW on top
@@ -114,8 +116,11 @@ class RemoteTransitionAdapter {
.setName(change.leash.toString() + "_transition-leash")
.setContainerLayer()
.setParent(
if (change.parent == null) info.rootLeash
else info.getChange(change.parent!!)!!.leash
if (change.parent == null) {
var rootIdx = info.findRootIndex(change.endDisplayId)
if (rootIdx < 0) rootIdx = 0
info.getRoot(rootIdx).leash
} else info.getChange(change.parent!!)!!.leash
)
.build()
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)

View File

@@ -321,7 +321,9 @@ public class RemoteTransitionCompat {
} else {
// We are receiving new opening tasks, so convert to onTasksAppeared.
targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
t.reparent(targets[i].leash, mInfo.getRootLeash());
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(targets[i].leash, layer);
mOpeningTasks.add(new TaskState(change, targets[i].leash));
}

View File

@@ -134,7 +134,7 @@ public class RemoteTransitionTest extends SysuiTestCase {
TransitionInfoBuilder(@WindowManager.TransitionType int type) {
mInfo = new TransitionInfo(type, 0 /* flags */);
mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
mInfo.addRootLeash(0, createMockSurface(true /* valid */), 0, 0);
}
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -144,6 +144,7 @@ public class RemoteTransitionTest extends SysuiTestCase {
change.setMode(mode);
change.setFlags(flags);
change.setTaskInfo(taskInfo);
change.setDisplayId(0, 0);
mInfo.addChange(change);
return this;
}

View File

@@ -679,7 +679,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
* needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
* Additionally, this gives shell the ability to better deal with merged transitions.
*/
private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
final Point tmpPos = new Point();
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
@@ -732,8 +732,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
} finally {
mController.mBuildingFinishLayers = false;
}
if (rootLeash.isValid()) {
t.reparent(rootLeash, null);
for (int i = 0; i < info.getRootCount(); ++i) {
t.reparent(info.getRoot(i).getLeash(), null);
}
}
@@ -1070,13 +1070,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
return;
}
if (mTargetDisplays.isEmpty()) {
mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
}
// While there can be multiple DC's involved. For now, we just use the first one as
// the "primary" one for most things. Eventually, this will need to change, but, for the
// time being, we don't have full cross-display transitions so it isn't a problem.
final DisplayContent dc = mTargetDisplays.get(0);
// Commit the visibility of visible activities before calculateTransitionInfo(), so the
// TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
@@ -1086,6 +1079,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mState == STATE_ABORT) {
mController.abort(this);
// Fall-back to the default display if there isn't one participating.
final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
: mController.mAtm.mRootWindowContainer.getDefaultDisplay();
dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
@@ -1098,16 +1094,24 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
mController.moveToPlaying(this);
if (dc.isKeyguardLocked()) {
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
// Check whether the participants were animated from back navigation.
final boolean markBackAnimated = mController.mAtm.mBackNavigationController
.containsBackAnimationTargets(this);
// Resolve the animating targets from the participants
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
// Repopulate the displays based on the resolved targets.
mTargetDisplays.clear();
for (int i = 0; i < info.getRootCount(); ++i) {
final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
info.getRoot(i).getDisplayId());
mTargetDisplays.add(dc);
if (dc.isKeyguardLocked()) {
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
}
if (markBackAnimated) {
mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
}
@@ -1129,9 +1133,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
// TODO(b/188669821): Move to animation impl in shell.
handleLegacyRecentsStartBehavior(dc, info);
for (int i = 0; i < mTargetDisplays.size(); ++i) {
handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
if (mRecentsDisplayId != INVALID_DISPLAY) break;
}
handleNonAppWindowsInTransition(dc, mType, mFlags);
handleNonAppWindowsInTransition(mType, mFlags);
// The callback is only populated for custom activity-level client animations
sendRemoteCallback(mClientAnimationStartCallback);
@@ -1195,11 +1202,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// This is non-null only if display has changes. It handles the visible windows that don't
// need to be participated in the transition.
final AsyncRotationController controller = dc.getAsyncRotationController();
if (controller != null && containsChangeFor(dc, mTargets)) {
controller.setupStartTransaction(transaction);
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AsyncRotationController controller = dc.getAsyncRotationController();
if (controller != null && containsChangeFor(dc, mTargets)) {
controller.setupStartTransaction(transaction);
}
}
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
buildFinishTransaction(mFinishTransaction, info);
if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
@@ -1220,10 +1230,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// client, we should finish and apply it here so the transactions aren't lost.
postCleanupOnFailure();
}
final AccessibilityController accessibilityController =
dc.mWmService.mAccessibilityController;
if (accessibilityController.hasCallbacks()) {
accessibilityController.onWMTransition(dc.getDisplayId(), mType);
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AccessibilityController accessibilityController =
dc.mWmService.mAccessibilityController;
if (accessibilityController.hasCallbacks()) {
accessibilityController.onWMTransition(dc.getDisplayId(), mType);
}
}
} else {
// No player registered or it's not enabled, so just finish/apply immediately
@@ -1300,16 +1313,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
return;
}
mRecentsDisplayId = dc.mDisplayId;
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
ActivityRecord recentsActivity = null;
if (recentsAnimationInputConsumer != null) {
// find the top-most going-away activity and the recents activity. The top-most
// is used as layer reference while the recents is used for registering the consumer
// override.
ActivityRecord recentsActivity = null;
ActivityRecord topActivity = null;
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -1333,6 +1345,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
if (recentsActivity == null) {
// No recents activity on `dc`, its probably on a different display.
return;
}
mRecentsDisplayId = dc.mDisplayId;
// The rest of this function handles nav-bar reparenting
if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
@@ -1427,7 +1445,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
private void handleNonAppWindowsInTransition(
@TransitionType int transit, @TransitionFlags int flags) {
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
// If the occlusion changed but the transition isn't an occlude/unocclude transition,
@@ -1805,6 +1823,41 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return wc instanceof DisplayContent;
}
private static int getDisplayId(@NonNull WindowContainer wc) {
return wc.getDisplayContent() != null
? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
}
@VisibleForTesting
static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
ArrayList<ChangeInfo> sortedTargets,
@NonNull SurfaceControl.Transaction startT) {
// There needs to be a root on each display.
for (int i = 0; i < sortedTargets.size(); ++i) {
final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
// Don't include wallpapers since they are in a different DA.
if (isWallpaper(wc)) continue;
final int endDisplayId = getDisplayId(wc);
if (endDisplayId < 0) continue;
// Check if Root was already created for this display with a higher-Z window
if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
// Make leash based on highest (z-order) direct child of ancestor with a participant.
WindowContainer leashReference = wc;
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
startT.setLayer(rootLeash, leashReference.getLastLayer());
outInfo.addRootLeash(endDisplayId, rootLeash,
ancestor.getBounds().left, ancestor.getBounds().top);
}
}
/**
* Construct a TransitionInfo object from a set of targets and changes. Also populates the
* root surface.
@@ -1815,37 +1868,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
ArrayList<ChangeInfo> sortedTargets,
@Nullable SurfaceControl.Transaction startT) {
@NonNull SurfaceControl.Transaction startT) {
final TransitionInfo out = new TransitionInfo(type, flags);
WindowContainer<?> topApp = null;
for (int i = 0; i < sortedTargets.size(); i++) {
final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
if (!isWallpaper(wc)) {
topApp = wc;
break;
}
}
if (topApp == null) {
out.setRootLeash(new SurfaceControl(), 0, 0);
calculateTransitionRoots(out, sortedTargets, startT);
if (out.getRootCount() == 0) {
return out;
}
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
// Make leash based on highest (z-order) direct child of ancestor with a participant.
// TODO(b/261418859): Handle the case when the target contains window containers which
// belong to a different display. As a workaround we use topApp, from which wallpaper
// window container is removed, instead of sortedTargets here.
WindowContainer leashReference = topApp;
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
startT.setLayer(rootLeash, leashReference.getLastLayer());
out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
@@ -1865,6 +1894,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
change.setDisplayId(info.mDisplayId, getDisplayId(target));
final Task task = target.asTask();
final TaskFragment taskFragment = target.asTaskFragment();
@@ -1951,6 +1981,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
TransitionInfo.AnimationOptions animOptions = null;
// Check if the top-most app is an activity (ie. activity->activity). If so, make sure to
// honor its custom transition options.
WindowContainer<?> topApp = null;
for (int i = 0; i < sortedTargets.size(); i++) {
if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
topApp = sortedTargets.get(i).mContainer;
break;
}
if (topApp.asActivityRecord() != null) {
final ActivityRecord topActivity = topApp.asActivityRecord();
animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
@@ -2001,14 +2040,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static WindowContainer<?> findCommonAncestor(
@NonNull ArrayList<ChangeInfo> targets,
@NonNull WindowContainer<?> topApp) {
final int displayId = getDisplayId(topApp);
WindowContainer<?> ancestor = topApp.getParent();
// Go up ancestor parent chain until all targets are descendants. Ancestor should never be
// null because all targets are attached.
for (int i = targets.size() - 1; i >= 0; i--) {
final ChangeInfo change = targets.get(i);
final WindowContainer wc = change.mContainer;
if (isWallpaper(wc)) {
// Skip the non-app window.
if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
// Skip the non-app window or windows on a different display
continue;
}
while (!wc.isDescendantOf(ancestor)) {
@@ -2184,6 +2224,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final Rect mAbsoluteBounds = new Rect();
boolean mShowWallpaper;
int mRotation = ROTATION_UNDEFINED;
int mDisplayId = -1;
@ActivityInfo.Config int mKnownConfigChanges;
/** These are just extra info. They aren't used for change-detection. */
@@ -2201,6 +2242,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mShowWallpaper = origState.showWallpaper();
mRotation = origState.getWindowConfiguration().getRotation();
mStartParent = origState.getParent();
mDisplayId = getDisplayId(origState);
}
@VisibleForTesting
@@ -2232,7 +2274,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// assume no change in windowing-mode.
|| (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
|| !mContainer.getBounds().equals(mAbsoluteBounds)
|| mRotation != mContainer.getWindowConfiguration().getRotation();
|| mRotation != mContainer.getWindowConfiguration().getRotation()
|| mDisplayId != getDisplayId(mContainer);
}
@TransitionInfo.TransitionMode

View File

@@ -519,6 +519,47 @@ public class TransitionTests extends WindowTestsBase {
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
}
@Test
public void testCreateInfo_MultiDisplay() {
DisplayContent otherDisplay = createNewDisplay();
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task display0Task = createTask(mDisplayContent);
final Task display1Task = createTask(otherDisplay);
// Start states.
changes.put(display0Task,
new Transition.ChangeInfo(display0Task, false /* vis */, true /* exChg */));
changes.put(display1Task,
new Transition.ChangeInfo(display1Task, false /* vis */, true /* exChg */));
fillChangeMap(changes, display0Task);
fillChangeMap(changes, display1Task);
// End states.
display0Task.setVisibleRequested(true);
display1Task.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
participants.add(display0Task);
participants.add(display1Task);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getRootCount());
// Check that the changes are assigned to the correct display
assertEquals(mDisplayContent.getDisplayId(), info.getChange(
display0Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
assertEquals(otherDisplay.getDisplayId(), info.getChange(
display1Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
// Check that roots can be found by display and have the correct display
assertEquals(mDisplayContent.getDisplayId(),
info.getRoot(info.findRootIndex(mDisplayContent.getDisplayId())).getDisplayId());
assertEquals(otherDisplay.getDisplayId(),
info.getRoot(info.findRootIndex(otherDisplay.getDisplayId())).getDisplayId());
}
@Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);