diff options
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()); + } + } } |