summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Rosky <erosky@google.com> 2020-10-27 15:18:15 -0700
committer Evan Rosky <erosky@google.com> 2020-11-18 13:00:13 -0800
commit8cfc4c2b64a89220f9aed2508b596dabb5731f37 (patch)
tree63cb59d034bc179775a901e0e794e05cdcde88c4
parent961f737505c854b783de6e08b1c5d49e64f143e3 (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
-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());
+ }
+ }
}