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
This commit is contained in:
Evan Rosky
2020-10-27 15:18:15 -07:00
parent 961f737505
commit 8cfc4c2b64
6 changed files with 342 additions and 72 deletions

View File

@@ -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 + "}";
}
}
}

View File

@@ -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();

View File

@@ -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.
@@ -90,6 +90,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private final TransitionController mController;
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
@@ -97,7 +103,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
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)

View File

@@ -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 */

View File

@@ -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

View File

@@ -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());
}
}
}