diff options
| author | 2020-10-27 15:18:15 -0700 | |
|---|---|---|
| committer | 2020-11-18 13:00:13 -0800 | |
| commit | 8cfc4c2b64a89220f9aed2508b596dabb5731f37 (patch) | |
| tree | 63cb59d034bc179775a901e0e794e05cdcde88c4 | |
| parent | 961f737505c854b783de6e08b1c5d49e64f143e3 (diff) | |
Calculate and provide more info necessary for animations
This adds relative offsets so that animating surfaces can
be reparented with the knowledge of how to position them
within their new parents.
This also creates a "rootleash" which serves as a place for the
transition player to put animating surfaces into where they
can all be siblings. This provides control over z-ordering
during the animation and removes relative cropping
constraints. This rootleash surface is a child of the
deepest container that is an ancestor to all animating
targets. It's layer is picked to be the top-most(z) of the
ancestor's direct children which contains animating
targets.
This then Updates the example transition in shell to use
the rootleash.
Bug: 169035082
Test: atest TransitionTests
Change-Id: If40872138cbb6087b46be4a93c298bad94e88ca1
6 files changed, 342 insertions, 72 deletions
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 0eef84765890..b4e7d6a9269f 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -19,6 +19,7 @@ package android.window; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -66,6 +67,9 @@ public final class TransitionInfo implements Parcelable { private final @WindowManager.TransitionOldType int mType; private final ArrayList<Change> mChanges = new ArrayList<>(); + private SurfaceControl mRootLeash; + private final Point mRootOffset = new Point(); + /** @hide */ public TransitionInfo(@WindowManager.TransitionOldType int type) { mType = type; @@ -74,6 +78,9 @@ public final class TransitionInfo implements Parcelable { private TransitionInfo(Parcel in) { mType = in.readInt(); in.readList(mChanges, null /* classLoader */); + mRootLeash = new SurfaceControl(); + mRootLeash.readFromParcel(in); + mRootOffset.readFromParcel(in); } @Override @@ -81,6 +88,8 @@ public final class TransitionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); dest.writeList(mChanges); + mRootLeash.writeToParcel(dest, flags); + mRootOffset.writeToParcel(dest, flags); } @NonNull @@ -103,10 +112,35 @@ 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); + } + public int getType() { return mType; } + /** + * @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. + */ + @NonNull + public SurfaceControl getRootLeash() { + if (mRootLeash == null) { + throw new IllegalStateException("Trying to get a leash which wasn't set"); + } + return mRootLeash; + } + + /** @return the offset (relative to the screen) of the root leash. */ + @NonNull + public Point getRootOffset() { + return mRootOffset; + } + @NonNull public List<Change> getChanges() { return mChanges; @@ -136,7 +170,7 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("{t=" + mType + " c=["); + sb.append("{t=" + mType + " ro=" + mRootOffset + " c=["); for (int i = 0; i < mChanges.size(); ++i) { if (i > 0) { sb.append(','); @@ -167,8 +201,9 @@ public final class TransitionInfo implements Parcelable { private WindowContainerToken mParent; private final SurfaceControl mLeash; private int mMode = TRANSIT_NONE; - private final Rect mStartBounds = new Rect(); - private final Rect mEndBounds = new Rect(); + private final Rect mStartAbsBounds = new Rect(); + private final Rect mEndAbsBounds = new Rect(); + private final Point mEndRelOffset = new Point(); public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -181,8 +216,9 @@ public final class TransitionInfo implements Parcelable { mLeash = new SurfaceControl(); mLeash.readFromParcel(in); mMode = in.readInt(); - mStartBounds.readFromParcel(in); - mEndBounds.readFromParcel(in); + mStartAbsBounds.readFromParcel(in); + mEndAbsBounds.readFromParcel(in); + mEndRelOffset.readFromParcel(in); } /** Sets the parent of this change's container. The parent must be a participant or null. */ @@ -195,14 +231,19 @@ public final class TransitionInfo implements Parcelable { mMode = mode; } - /** Sets the bounds this container occupied before the change */ - public void setStartBounds(@Nullable Rect rect) { - mStartBounds.set(rect); + /** Sets the bounds this container occupied before the change in screen space */ + public void setStartAbsBounds(@Nullable Rect rect) { + mStartAbsBounds.set(rect); } - /** Sets the bounds this container will occupy after the change */ - public void setEndBounds(@Nullable Rect rect) { - mEndBounds.set(rect); + /** Sets the bounds this container will occupy after the change in screen space */ + public void setEndAbsBounds(@Nullable Rect rect) { + mEndAbsBounds.set(rect); + } + + /** Sets the offset of this container from its parent surface */ + public void setEndRelOffset(int left, int top) { + mEndRelOffset.set(left, top); } /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @@ -230,8 +271,8 @@ public final class TransitionInfo implements Parcelable { * is coming into existence. */ @NonNull - public Rect getStartBounds() { - return mStartBounds; + public Rect getStartAbsBounds() { + return mStartAbsBounds; } /** @@ -239,8 +280,16 @@ public final class TransitionInfo implements Parcelable { * is disappearing. */ @NonNull - public Rect getEndBounds() { - return mEndBounds; + public Rect getEndAbsBounds() { + return mEndAbsBounds; + } + + /** + * @return the offset of the container's surface from its parent surface after the change. + */ + @NonNull + public Point getEndRelOffset() { + return mEndRelOffset; } /** @return the leash or surface to animate for this container */ @@ -256,8 +305,9 @@ public final class TransitionInfo implements Parcelable { dest.writeTypedObject(mParent, flags); mLeash.writeToParcel(dest, flags); dest.writeInt(mMode); - mStartBounds.writeToParcel(dest, flags); - mEndBounds.writeToParcel(dest, flags); + mStartAbsBounds.writeToParcel(dest, flags); + mEndAbsBounds.writeToParcel(dest, flags); + mEndRelOffset.writeToParcel(dest, flags); } @NonNull @@ -283,8 +333,8 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { return "{" + mContainer + "(" + mParent + ") leash=" + mLeash - + " m=" + modeToString(mMode) + " sb=" + mStartBounds - + " eb=" + mEndBounds + "}"; + + " m=" + modeToString(mMode) + " sb=" + mStartAbsBounds + + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + "}"; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java index 388eb28223dc..120039de1240 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java @@ -116,7 +116,7 @@ public class Transitions extends ITransitionPlayer.Stub { } @Override - public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info, + public void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); @@ -131,22 +131,53 @@ public class Transitions extends ITransitionPlayer.Stub { + transitionToken); } mActiveTransitions.put(transitionToken, new ArrayList<>()); - for (int i = 0; i < info.getChanges().size(); ++i) { - final SurfaceControl leash = info.getChanges().get(i).getLeash(); + boolean isOpening = isOpeningType(info.getType()); + if (info.getRootLeash().isValid()) { + t.show(info.getRootLeash()); + } + // changes should be ordered top-to-bottom in z + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); + + // Don't animate anything with an animating parent + if (change.getParent() != null) { + if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) { + t.show(leash); + t.setMatrix(leash, 1, 0, 0, 1); + } + continue; + } + + t.reparent(leash, info.getRootLeash()); + t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x, + change.getEndAbsBounds().top - info.getRootOffset().y); + // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) { t.show(leash); t.setMatrix(leash, 1, 0, 0, 1); - if (isOpeningType(info.getType())) { + if (isOpening) { + // put on top and fade in + t.setLayer(leash, info.getChanges().size() - i); t.setAlpha(leash, 0.f); startExampleAnimation(transitionToken, leash, true /* show */); } else { + // put on bottom and leave it visible without fade + t.setLayer(leash, -i); t.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) { - if (!isOpeningType(info.getType())) { + if (isOpening) { + // put on bottom and leave visible without fade + t.setLayer(leash, -i); + } else { + // put on top and fade out + t.setLayer(leash, info.getChanges().size() - i); startExampleAnimation(transitionToken, leash, false /* show */); } + } else { + t.setLayer(leash, info.getChanges().size() - i); } } t.apply(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 341694d8ecb2..0cdd055d4052 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import android.annotation.IntDef; import android.annotation.NonNull; +import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -46,7 +47,6 @@ import com.android.internal.protolog.common.ProtoLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Set; /** * Represents a logical transition. @@ -91,13 +91,24 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private final BLASTSyncEngine mSyncEngine; /** + * This is a leash to put animating surfaces into flatly without clipping/ordering issues. It + * is a child of all the targets' shared ancestor. + */ + private SurfaceControl mRootLeash = null; + + /** * Contains change infos for both participants and all ancestors. We have to track ancestors * because they are all promotion candidates and thus we need their start-states * to be captured. */ final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); + /** The collected participants in the transition. */ final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); + + /** The final animation targets derived from participants after promotion. */ + private ArraySet<WindowContainer> mTargets = null; + private @TransitionState int mState = STATE_COLLECTING; private boolean mReadyCalled = false; @@ -190,6 +201,42 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mState < STATE_PLAYING) { throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); } + final Point tmpPos = new Point(); + // usually only size 1 + final ArraySet<DisplayContent> displays = new ArraySet<>(); + // Immediately apply all surface reparents, don't wait for pending/sync/etc. + SurfaceControl.Transaction t = mController.mAtm.mWindowManager.mTransactionFactory.get(); + for (int i = mTargets.size() - 1; i >= 0; --i) { + final WindowContainer target = mTargets.valueAt(i); + if (target.getParent() != null) { + // Ensure surfaceControls are re-parented back into the hierarchy. + t.reparent(target.getSurfaceControl(), target.getParent().getSurfaceControl()); + target.getRelativePosition(tmpPos); + t.setPosition(target.getSurfaceControl(), tmpPos.x, tmpPos.y); + displays.add(target.getDisplayContent()); + } + } + // Need to update layers on ALL displays (for now) since they were all paused while + // the animation played. + for (int i = displays.size() - 1; i >= 0; --i) { + if (displays.valueAt(i) == null) continue; + displays.valueAt(i).assignChildLayers(t); + } + // Also pro-actively hide going-invisible activity surfaces in same transaction to + // prevent flickers due to reparenting and animation z-order mismatch. + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); + if (ar == null || ar.mVisibleRequested || !ar.isVisible()) continue; + t.hide(ar.getSurfaceControl()); + } + if (mRootLeash.isValid()) { + t.remove(mRootLeash); + } + mRootLeash = null; + t.apply(); + t.close(); + + // Commit all going-invisible containers for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); if (ar == null || ar.mVisibleRequested) { @@ -234,7 +281,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mState = STATE_PLAYING; mController.moveToPlaying(this); - final TransitionInfo info = calculateTransitionInfo(mType, mParticipants, mChanges); + + // Resolve the animating targets from the participants + mTargets = calculateTargets(mParticipants, mChanges); + final TransitionInfo info = calculateTransitionInfo(mType, mTargets, mChanges); + mRootLeash = info.getRootLeash(); handleNonAppWindowsInTransition(displayId, mType, mFlags); @@ -245,11 +296,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.getTransitionPlayer().onTransitionReady(this, info, transaction); } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the - // client, we should immediately apply it here so the transactions aren't lost. + // client, we should finish and apply it here so the transactions aren't lost. transaction.apply(); + finishTransition(); } } else { + // No player registered, so just finish/apply immediately transaction.apply(); + finishTransition(); } mSyncId = -1; } @@ -325,10 +379,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe * * @return {@code true} if transition in target can be promoted to its parent. */ - private static boolean canPromote( - WindowContainer target, ArraySet<WindowContainer> topTargets) { + private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, + ArrayMap<WindowContainer, ChangeInfo> changes) { final WindowContainer parent = target.getParent(); - if (parent == null || !parent.canCreateRemoteAnimationTarget()) { + final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null; + if (parent == null || !parent.canCreateRemoteAnimationTarget() + || parentChanges == null || !parentChanges.hasChanged(parent)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", parent == null ? "no parent" : ("parent can't be target " + parent)); return false; @@ -394,14 +450,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Go through each target until we find one that can be promoted. for (WindowContainer targ : topTargets) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); - if (!canPromote(targ, topTargets)) { + if (!canPromote(targ, topTargets, changes)) { continue; } - final WindowContainer parent = targ.getParent(); // No obstructions found to promotion, so promote + final WindowContainer parent = targ.getParent(); + final ChangeInfo parentInfo = changes.get(parent); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " CAN PROMOTE: promoting to parent %s", parent); - final ChangeInfo parentInfo = changes.get(parent); targets.add(parent); // Go through all children of newly-promoted container and remove them from the @@ -443,10 +499,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe * animation targets to higher level in the window hierarchy if possible. */ @VisibleForTesting - static TransitionInfo calculateTransitionInfo(int type, Set<WindowContainer> participants, + @NonNull + static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes) { - final TransitionInfo out = new TransitionInfo(type); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start calculating TransitionInfo based on participants: %s", participants); @@ -470,6 +525,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Search through ancestors to find the top-most participant (if one exists) WindowContainer topParent = null; tmpList.clear(); + if (reportIfNotTop(wc)) { + tmpList.add(wc); + } for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { if (participants.contains(p)) { topParent = p; @@ -479,8 +537,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } if (topParent != null) { - // There was an ancestor participant, so don't add wc to targets. However, continue - // to add any always-report parents along the way. + // There was an ancestor participant, so don't add wc to targets unless always- + // report. Similarly, add any always-report parents along the way. for (int i = 0; i < tmpList.size(); ++i) { targets.add(tmpList.get(i)); final ChangeInfo info = changes.get(tmpList.get(i)); @@ -508,10 +566,70 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe while (tryPromote(topTargets, targets, changes)) { // Empty on purpose } + return targets; + } - // Convert all the resolved ChangeInfos into a TransactionInfo object. - for (int i = targets.size() - 1; i >= 0; --i) { - final WindowContainer target = targets.valueAt(i); + /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */ + private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, + ArrayList<WindowContainer> out) { + for (int i = root.getChildCount() - 1; i >= 0; --i) { + final WindowContainer child = root.getChildAt(i); + addMembersInOrder(child, members, out); + if (members.contains(child)) { + out.add(child); + } + } + } + + /** + * Construct a TransitionInfo object from a set of targets and changes. Also populates the + * root surface. + */ + @VisibleForTesting + @NonNull + static TransitionInfo calculateTransitionInfo(int type, ArraySet<WindowContainer> targets, + ArrayMap<WindowContainer, ChangeInfo> changes) { + final TransitionInfo out = new TransitionInfo(type); + if (targets.isEmpty()) { + out.setRootLeash(new SurfaceControl(), 0, 0); + return out; + } + + // Find the top-most shared ancestor + WindowContainer ancestor = targets.valueAt(0).getParent(); + // Go up ancestor parent chain until all topTargets are descendants. + ancestorLoop: + while (ancestor != null) { + for (int i = 1; i < targets.size(); ++i) { + if (!targets.valueAt(i).isDescendantOf(ancestor)) { + ancestor = ancestor.getParent(); + continue ancestorLoop; + } + } + break; + } + + // Sort targets top-to-bottom in Z. + ArrayList<WindowContainer> sortedTargets = new ArrayList<>(); + addMembersInOrder(ancestor, targets, sortedTargets); + + // make leash based on highest (z-order) direct child of ancestor with a participant. + WindowContainer leashReference = sortedTargets.get(0); + while (leashReference.getParent() != ancestor) { + leashReference = leashReference.getParent(); + } + final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( + "Transition Root: " + leashReference.getName()).build(); + SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get(); + t.setLayer(rootLeash, leashReference.getLastLayer()); + t.apply(); + t.close(); + 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) { + final WindowContainer target = sortedTargets.get(i); final ChangeInfo info = changes.get(target); final TransitionInfo.Change change = new TransitionInfo.Change( target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() @@ -520,8 +638,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); } change.setMode(info.getTransitMode(target)); - change.setStartBounds(info.mAbsoluteBounds); - change.setEndBounds(target.getBounds()); + change.setStartAbsBounds(info.mAbsoluteBounds); + change.setEndAbsBounds(target.getBounds()); + change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left, + target.getBounds().top - target.getParent().getBounds().top); out.addChange(change); } @@ -543,23 +663,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // before change state boolean mVisible; int mWindowingMode; - Rect mAbsoluteBounds; + final Rect mAbsoluteBounds = new Rect(); ChangeInfo(@NonNull WindowContainer origState) { mVisible = origState.isVisibleRequested(); mWindowingMode = origState.getWindowingMode(); - mAbsoluteBounds = origState.getBounds(); + mAbsoluteBounds.set(origState.getBounds()); } @VisibleForTesting ChangeInfo(boolean visible, boolean existChange) { mVisible = visible; - mAbsoluteBounds = new Rect(); mExistenceChanged = existChange; } boolean hasChanged(@NonNull WindowContainer newState) { - return newState.isVisibleRequested() != mVisible + // If it's invisible and hasn't changed visibility, always return false since even if + // something changed, it wouldn't be a visible change. + final boolean currVisible = newState.isVisibleRequested(); + if (currVisible == mVisible && !mVisible) return false; + return currVisible != mVisible // if mWindowingMode is 0, this container wasn't attached at collect time, so // assume no change in windowing-mode. || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode) diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 890ae8f2a434..2f5d10afe3da 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -109,10 +109,18 @@ class TransitionController { return mCollectingTransition != null; } + /** + * @return {@code true} if transition is actively playing. This is not necessarily {@code true} + * during collection. + */ + boolean isPlaying() { + return !mPlayingTransitions.isEmpty(); + } + /** @return {@code true} if a transition is running */ boolean inTransition() { // TODO(shell-transitions): eventually properly support multiple - return mCollectingTransition != null || !mPlayingTransitions.isEmpty(); + return isCollecting() || isPlaying(); } /** @return {@code true} if wc is in a participant subtree */ diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index b25fbc0e18a3..440445abbfad 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2069,6 +2069,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void assignLayer(Transaction t, int layer) { + // Don't assign layers while a transition animation is playing + // TODO(b/173528115): establish robust best-practices around z-order fighting. + if (mWmService.mAtmService.getTransitionController().isPlaying()) return; final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null; if (mSurfaceControl != null && changed) { setLayer(t, layer); @@ -2093,6 +2096,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceAnimator.setLayer(t, layer); } + int getLastLayer() { + return mLastLayer; + } + protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { // Route through surface animator to accommodate that our surface control might be 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 165d4681c9e0..4909b1d7629e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.window.TransitionInfo.TRANSIT_HIDE; @@ -46,16 +47,24 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) public class TransitionTests extends WindowTestsBase { + private Transition createTestTransition(int transitType) { + TransitionController controller = mock(TransitionController.class); + BLASTSyncEngine sync = new BLASTSyncEngine(mWm); + return new Transition(transitType, 0 /* flags */, controller, sync); + } + @Test public void testCreateInfo_NewTask() { + final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + ArraySet<WindowContainer> participants = transition.mParticipants; + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); final ActivityRecord closing = createActivityRecord(oldTask); final ActivityRecord opening = createActivityRecord(newTask); - ArrayMap<WindowContainer, Transition.ChangeInfo> changes = new ArrayMap<>(); - ArraySet<WindowContainer> participants = new ArraySet(); // Start states. changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); @@ -70,29 +79,32 @@ public class TransitionTests extends WindowTestsBase { // Check basic both tasks participating participants.add(oldTask); participants.add(newTask); - TransitionInfo info = - Transition.calculateTransitionInfo(transitType, participants, changes); + ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertEquals(transitType, info.getType()); // Check that children are pruned participants.add(opening); participants.add(closing); - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); // Check combined prune and promote participants.remove(newTask); - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); // Check multi promote participants.remove(oldTask); - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); @@ -100,6 +112,10 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_NestedTasks() { + final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + ArraySet<WindowContainer> participants = transition.mParticipants; + final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); final Task newNestedTask = createTaskInStack(newTask, 0); @@ -109,8 +125,6 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord closing = createActivityRecord(oldTask); final ActivityRecord opening = createActivityRecord(newNestedTask); final ActivityRecord opening2 = createActivityRecord(newNestedTask2); - ArrayMap<WindowContainer, Transition.ChangeInfo> changes = new ArrayMap<>(); - ArraySet<WindowContainer> participants = new ArraySet(); // Start states. changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(newNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); @@ -130,8 +144,8 @@ public class TransitionTests extends WindowTestsBase { participants.add(oldTask); participants.add(opening); participants.add(opening2); - TransitionInfo info = - Transition.calculateTransitionInfo(transitType, participants, changes); + ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertEquals(transitType, info.getType()); assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); @@ -139,7 +153,8 @@ public class TransitionTests extends WindowTestsBase { // Check that unchanging but visible descendant of sibling prevents promotion participants.remove(opening2); - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken())); assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); @@ -147,6 +162,9 @@ public class TransitionTests extends WindowTestsBase { @Test public void testCreateInfo_DisplayArea() { + final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; + ArraySet<WindowContainer> participants = transition.mParticipants; final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); final Task showNestedTask = createTaskInStack(showTask, 0); @@ -155,8 +173,6 @@ public class TransitionTests extends WindowTestsBase { final DisplayArea tda = showTask.getDisplayArea(); final ActivityRecord showing = createActivityRecord(showNestedTask); final ActivityRecord showing2 = createActivityRecord(showTask2); - ArrayMap<WindowContainer, Transition.ChangeInfo> changes = new ArrayMap<>(); - ArraySet<WindowContainer> participants = new ArraySet(); // Start states. changes.put(showTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); changes.put(showNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); @@ -173,8 +189,8 @@ public class TransitionTests extends WindowTestsBase { // Check promotion to DisplayArea participants.add(showing); participants.add(showing2); - TransitionInfo info = - Transition.calculateTransitionInfo(transitType, participants, changes); + ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(1, info.getChanges().size()); assertEquals(transitType, info.getType()); assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); @@ -182,22 +198,21 @@ public class TransitionTests extends WindowTestsBase { ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); // Check that organized tasks get reported even if not top showTask.mTaskOrganizer = mockOrg; - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken())); // Even if DisplayArea explicitly participating participants.add(tda); - info = Transition.calculateTransitionInfo(transitType, participants, changes); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transitType, targets, changes); assertEquals(2, info.getChanges().size()); } @Test public void testCreateInfo_existenceChange() { - TransitionController controller = mock(TransitionController.class); - BLASTSyncEngine sync = new BLASTSyncEngine(mWm); - Transition transition = new Transition( - TRANSIT_OLD_TASK_OPEN, 0 /* flags */, controller, sync); + final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); @@ -214,8 +229,9 @@ public class TransitionTests extends WindowTestsBase { opening.mVisibleRequested = true; closing.mVisibleRequested = false; - TransitionInfo info = Transition.calculateTransitionInfo( - 0, transition.mParticipants, transition.mChanges); + ArraySet<WindowContainer> targets = Transition.calculateTargets( + transition.mParticipants, transition.mChanges); + TransitionInfo info = Transition.calculateTransitionInfo(0, targets, transition.mChanges); assertEquals(2, info.getChanges().size()); // There was an existence change on open, so it should be OPEN rather than SHOW assertEquals(TRANSIT_OPEN, @@ -224,4 +240,39 @@ public class TransitionTests extends WindowTestsBase { assertEquals(TRANSIT_HIDE, info.getChange(closeTask.mRemoteToken.toWindowContainerToken()).getMode()); } + + @Test + public void testCreateInfo_ordering() { + final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); + // pick some number with a high enough chance of being out-of-order when added to set. + final int taskCount = 6; + + final Task[] tasks = new Task[taskCount]; + for (int i = 0; i < taskCount; ++i) { + // Each add goes on top, so at the end of this, task[9] should be on top + tasks[i] = createTaskStackOnDisplay(WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, mDisplayContent); + final ActivityRecord act = createActivityRecord(tasks[i]); + // alternate so that the transition doesn't get promoted to the display area + act.mVisibleRequested = (i % 2) == 0; // starts invisible + } + + // doesn't matter which order collected since participants is a set + for (int i = 0; i < taskCount; ++i) { + transition.collectExistenceChange(tasks[i]); + final ActivityRecord act = tasks[i].getTopMostActivity(); + transition.collect(act); + tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0; + } + + ArraySet<WindowContainer> targets = Transition.calculateTargets( + transition.mParticipants, transition.mChanges); + TransitionInfo info = Transition.calculateTransitionInfo(0, targets, transition.mChanges); + assertEquals(taskCount, info.getChanges().size()); + // verify order is top-to-bottem + for (int i = 0; i < taskCount; ++i) { + assertEquals(tasks[taskCount - i - 1].mRemoteToken.toWindowContainerToken(), + info.getChanges().get(i).getContainer()); + } + } } |