diff options
13 files changed, 388 insertions, 123 deletions
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index b8bd7032f678..4c482460543a 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -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 @@ -184,9 +186,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; @@ -200,10 +200,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); } @@ -213,8 +210,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); } @@ -238,10 +234,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) { @@ -257,23 +258,52 @@ public final class TransitionInfo implements Parcelable { } /** + * @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() { @@ -320,8 +350,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(','); @@ -448,8 +485,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(); } } @@ -476,10 +513,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; } @@ -496,6 +534,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; /** @@ -526,6 +566,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(); @@ -546,6 +588,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; @@ -608,6 +652,12 @@ public final class TransitionInfo implements Parcelable { } /** 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; mEndRotation = end; @@ -725,6 +775,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; @@ -776,6 +834,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); @@ -822,6 +882,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); @@ -1108,4 +1173,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; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 87cf6559a0f0..45bb73bdc0d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -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); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index e1c089550c2d..e09c3c9e4d3f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 75112b62c1c6..2e864483bf1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -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; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index f66c26bb87e4..63c7969291a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -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. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 27b82c08d803..039bde95815e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -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; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java index 8c6e1e7f5f1b..7595c9617709 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java @@ -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; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java index 35c374ddd974..26b787fa836c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java @@ -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; } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index 6946e6bf88a8..03e1e66a3cac 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -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) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 6f7d66d03cab..58e7747a7a9f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -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)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java index 64e58d0de2b9..0e2a3acd3df4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java @@ -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; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 606f011ae55c..4e0f120759d9 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -675,7 +675,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<>(); @@ -728,8 +728,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); } } @@ -1066,13 +1066,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 @@ -1082,6 +1075,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; @@ -1094,16 +1090,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); } @@ -1125,9 +1129,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); @@ -1191,11 +1198,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 { @@ -1216,10 +1226,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 @@ -1296,16 +1309,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); @@ -1329,6 +1341,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() @@ -1423,7 +1441,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, @@ -1801,6 +1819,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. @@ -1811,37 +1864,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) { @@ -1861,6 +1890,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(); @@ -1947,6 +1977,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); @@ -1997,14 +2036,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)) { @@ -2180,6 +2220,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. */ @@ -2197,6 +2238,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mShowWallpaper = origState.showWallpaper(); mRotation = origState.getWindowConfiguration().getRotation(); mStartParent = origState.getParent(); + mDisplayId = getDisplayId(origState); } @VisibleForTesting @@ -2228,7 +2270,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 diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index debfc841e52d..304581297932 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -520,6 +520,47 @@ public class TransitionTests extends WindowTestsBase { } @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); |