summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/window/TransitionInfo.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java41
-rw-r--r--services/core/java/com/android/server/wm/Transition.java169
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java99
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());
+ }
+ }
}