summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Rosky <erosky@google.com> 2024-01-09 22:29:03 +0000
committer Evan Rosky <erosky@google.com> 2024-02-09 16:49:41 +0000
commitddcfd119a393e133f5831d1714cae7de001ab66e (patch)
treeb4699f8b8a9f49b35c6a31e5f18211714ab1dfdc
parentebdb4991c62cd0aff0b42155c73caf04d478d64b (diff)
Add Config-at-end directive/support to Transitions
This is basically a way to tell the transition system (and WM) that activities in a specific container should not have their clients receive configuration changes until the END of the transition animation. Usage amounts to calling setConfigAtEnd(wtoken) on a WCT. Bug: 202201326 Bug: 290992727 Test: atest TransitionTests#testConfigAtEnd Change-Id: If4728eab6686a1d0e081af128606af150e0d7cc2
-rw-r--r--core/java/android/window/TransitionInfo.java6
-rw-r--r--core/java/android/window/WindowContainerTransaction.java30
-rw-r--r--services/core/java/com/android/server/wm/Transition.java204
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java31
5 files changed, 238 insertions, 50 deletions
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index feae173f3e61..15b9b788bca9 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -159,8 +159,11 @@ public final class TransitionInfo implements Parcelable {
*/
public static final int FLAG_SYNC = 1 << 21;
+ /** This change represents its start configuration for the duration of the animation. */
+ public static final int FLAG_CONFIG_AT_END = 1 << 22;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 22;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 23;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -193,6 +196,7 @@ public final class TransitionInfo implements Parcelable {
FLAG_TASK_LAUNCHING_BEHIND,
FLAG_MOVED_TO_TOP,
FLAG_SYNC,
+ FLAG_CONFIG_AT_END,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index d9b5b2d725e2..efc71d7b43de 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -914,6 +914,23 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Defers client-facing configuration changes for activities in `container` until the end of
+ * the transition animation. The configuration will still be applied to the WMCore hierarchy
+ * at the normal time (beginning); so, special consideration must be made for this in the
+ * animation.
+ *
+ * @param container WindowContainerToken who's children should defer config notification.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction deferConfigToTransitionEnd(
+ @NonNull WindowContainerToken container) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mConfigAtTransitionEnd = true;
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -1050,6 +1067,7 @@ public final class WindowContainerTransaction implements Parcelable {
private Rect mBoundsChangeSurfaceBounds = null;
@Nullable
private Rect mRelativeBounds = null;
+ private boolean mConfigAtTransitionEnd = false;
private int mActivityWindowingMode = -1;
private int mWindowingMode = -1;
@@ -1082,6 +1100,7 @@ public final class WindowContainerTransaction implements Parcelable {
mRelativeBounds = new Rect();
mRelativeBounds.readFromParcel(in);
}
+ mConfigAtTransitionEnd = in.readBoolean();
mWindowingMode = in.readInt();
mActivityWindowingMode = in.readInt();
@@ -1134,6 +1153,8 @@ public final class WindowContainerTransaction implements Parcelable {
? other.mRelativeBounds
: new Rect(other.mRelativeBounds);
}
+ mConfigAtTransitionEnd = mConfigAtTransitionEnd
+ || other.mConfigAtTransitionEnd;
}
public int getWindowingMode() {
@@ -1191,6 +1212,11 @@ public final class WindowContainerTransaction implements Parcelable {
return mDragResizing;
}
+ /** Gets whether the config should be sent to the client at the end of the transition. */
+ public boolean getConfigAtTransitionEnd() {
+ return mConfigAtTransitionEnd;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1269,6 +1295,9 @@ public final class WindowContainerTransaction implements Parcelable {
if ((mChangeMask & CHANGE_RELATIVE_BOUNDS) != 0) {
sb.append("relativeBounds:").append(mRelativeBounds).append(",");
}
+ if (mConfigAtTransitionEnd) {
+ sb.append("configAtTransitionEnd").append(",");
+ }
sb.append("}");
return sb.toString();
}
@@ -1297,6 +1326,7 @@ public final class WindowContainerTransaction implements Parcelable {
if (mRelativeBounds != null) {
mRelativeBounds.writeToParcel(dest, flags);
}
+ dest.writeBoolean(mConfigAtTransitionEnd);
dest.writeInt(mWindowingMode);
dest.writeInt(mActivityWindowingMode);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2accf9a2a43a..5d9c42d4c3a8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -44,6 +44,7 @@ import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
+import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -65,6 +66,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -307,6 +309,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
*/
int mAnimationTrack = 0;
+ /**
+ * List of activities whose configurations are sent to the client at the end of the transition
+ * instead of immediately when the configuration changes.
+ */
+ ArrayList<ActivityRecord> mConfigAtEndActivities = null;
+
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
@@ -484,6 +492,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return mTargetDisplays.contains(dc);
}
+ void setConfigAtEnd(@NonNull WindowContainer<?> wc) {
+ wc.forAllActivities(ar -> {
+ if (!ar.isVisible() || !ar.isVisibleRequested()) return;
+ if (mConfigAtEndActivities == null) {
+ mConfigAtEndActivities = new ArrayList<>();
+ }
+ if (mConfigAtEndActivities.contains(ar)) {
+ return;
+ }
+ mConfigAtEndActivities.add(ar);
+ ar.pauseConfigurationDispatch();
+ });
+ snapshotStartState(wc);
+ mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+ }
+
/** Set a transition to be a seamless-rotation. */
void setSeamlessRotation(@NonNull WindowContainer wc) {
final ChangeInfo info = mChanges.get(wc);
@@ -644,20 +668,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
- // "snapshot" all parents (as potential promotion targets). Do this before checking
- // if this is already a participant in case it has since been re-parented.
- for (WindowContainer<?> curr = getAnimatableParent(wc);
- curr != null && !mChanges.containsKey(curr);
- curr = getAnimatableParent(curr)) {
- final ChangeInfo info = new ChangeInfo(curr);
- updateTransientFlags(info);
- mChanges.put(curr, info);
- if (isReadyGroup(curr)) {
- mReadyTrackerOld.addGroup(curr);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
- + " Transition %d with root=%s", mSyncId, curr);
- }
- }
+ // Snapshot before checking if this is a participant in case it has been re-parented.
+ snapshotStartState(getAnimatableParent(wc));
if (mParticipants.contains(wc)) return;
// Transient-hide may be hidden later, so no need to request redraw.
if (!isInTransientHide(wc)) {
@@ -688,6 +700,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
+ /** "snapshot" `wc` and all its parents (as potential promotion targets). */
+ private void snapshotStartState(@NonNull WindowContainer<?> wc) {
+ for (WindowContainer<?> curr = wc;
+ curr != null && !mChanges.containsKey(curr);
+ curr = getAnimatableParent(curr)) {
+ final ChangeInfo info = new ChangeInfo(curr);
+ updateTransientFlags(info);
+ mChanges.put(curr, info);
+ if (isReadyGroup(curr)) {
+ mReadyTrackerOld.addGroup(curr);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+ + " Transition %d with root=%s", mSyncId, curr);
+ }
+ }
+ }
+
private void updateTransientFlags(@NonNull ChangeInfo info) {
final WindowContainer<?> wc = info.mContainer;
// Only look at tasks, taskfragments, or activities
@@ -934,47 +962,60 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
/**
+ * Populates `t` with instructions to reset surface transform of `change` so it matches
+ * the WM hierarchy. This "undoes" lingering state left by the animation.
+ */
+ private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target,
+ SurfaceControl targetLeash) {
+ final Point tmpPos = new Point();
+ target.getRelativePosition(tmpPos);
+ t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
+ // No need to clip the display in case seeing the clipped content when during the
+ // display rotation. No need to clip activities because they rely on clipping on
+ // task layers.
+ if (target.asTaskFragment() == null) {
+ t.setCrop(targetLeash, null /* crop */);
+ } else {
+ // Crop to the resolved override bounds.
+ final Rect clipRect = target.getResolvedOverrideBounds();
+ t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
+ }
+ t.setMatrix(targetLeash, 1, 0, 0, 1);
+ // The bounds sent to the transition is always a real bounds. This means we lose
+ // information about "null" bounds (inheriting from parent). Core will fix-up
+ // non-organized window surface bounds; however, since Core can't touch organized
+ // surfaces, add the "inherit from parent" restoration here.
+ if (target.isOrganized() && target.matchParentBounds()) {
+ t.setWindowCrop(targetLeash, -1, -1);
+ }
+ }
+
+ /**
* Build a transaction that "resets" all the re-parenting and layer changes. This is
* intended to be applied at the end of the transition but before the finish callback. This
* needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
* Additionally, this gives shell the ability to better deal with merged transitions.
*/
private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
- final Point tmpPos = new Point();
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
for (int i = mTargets.size() - 1; i >= 0; --i) {
- final WindowContainer target = mTargets.get(i).mContainer;
- if (target.getParent() != null) {
- final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
- final SurfaceControl origParent = getOrigParentSurface(target);
- // Ensure surfaceControls are re-parented back into the hierarchy.
- t.reparent(targetLeash, origParent);
- t.setLayer(targetLeash, target.getLastLayer());
- target.getRelativePosition(tmpPos);
- t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
- // No need to clip the display in case seeing the clipped content when during the
- // display rotation. No need to clip activities because they rely on clipping on
- // task layers.
- if (target.asTaskFragment() == null) {
- t.setCrop(targetLeash, null /* crop */);
- } else {
- // Crop to the resolved override bounds.
- final Rect clipRect = target.getResolvedOverrideBounds();
- t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
- }
- t.setCornerRadius(targetLeash, 0);
- t.setShadowRadius(targetLeash, 0);
- t.setMatrix(targetLeash, 1, 0, 0, 1);
- t.setAlpha(targetLeash, 1);
- // The bounds sent to the transition is always a real bounds. This means we lose
- // information about "null" bounds (inheriting from parent). Core will fix-up
- // non-organized window surface bounds; however, since Core can't touch organized
- // surfaces, add the "inherit from parent" restoration here.
- if (target.isOrganized() && target.matchParentBounds()) {
- t.setWindowCrop(targetLeash, -1, -1);
- }
- displays.add(target.getDisplayContent());
+ final WindowContainer<?> target = mTargets.get(i).mContainer;
+ if (target.getParent() == null) continue;
+ final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
+ final SurfaceControl origParent = getOrigParentSurface(target);
+ // Ensure surfaceControls are re-parented back into the hierarchy.
+ t.reparent(targetLeash, origParent);
+ t.setLayer(targetLeash, target.getLastLayer());
+ t.setCornerRadius(targetLeash, 0);
+ t.setShadowRadius(targetLeash, 0);
+ t.setAlpha(targetLeash, 1);
+ displays.add(target.getDisplayContent());
+ // For config-at-end, the end-transform will be reset after the config is actually
+ // applied in the client (since the transform depends on config). The other properties
+ // remain here because shell might want to persistently override them.
+ if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
+ resetSurfaceTransform(t, target, targetLeash);
}
}
// Remove screenshot layers if necessary
@@ -1304,6 +1345,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.mAtm.mRootWindowContainer.rankTaskLayers();
}
+ commitConfigAtEndActivities();
+
// dispatch legacy callback in a different loop. This is because multiple legacy handlers
// (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
// processed all the participants first (in particular, we want to trigger pip-enter first)
@@ -1421,6 +1464,52 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.updateAnimatingState();
}
+ private void commitConfigAtEndActivities() {
+ if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) {
+ return;
+ }
+ final SurfaceControl.Transaction t =
+ mController.mAtm.mWindowManager.mTransactionFactory.get();
+ for (int i = 0; i < mTargets.size(); ++i) {
+ final WindowContainer target = mTargets.get(i).mContainer;
+ if (target.getParent() == null || (mTargets.get(i).mFlags
+ & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
+ continue;
+ }
+ final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
+ // Reset surface state here (since it was skipped in buildFinishTransaction). Since
+ // we are resuming config to the "current" state, we have to calculate the matching
+ // surface state now (rather than snapshotting it at animation start).
+ resetSurfaceTransform(t, target, targetLeash);
+ }
+
+ // Now we resume the configuration dispatch, wait until the now resumed configs have been
+ // drawn, and then apply everything together.
+ final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
+ new BLASTSyncEngine.TransactionReadyListener() {
+ @Override
+ public void onTransactionReady(int mSyncId,
+ SurfaceControl.Transaction transaction) {
+ t.merge(transaction);
+ t.apply();
+ }
+
+ @Override
+ public void onTransactionCommitTimeout() {
+ t.apply();
+ }
+ }, "ConfigAtTransitEnd");
+ final int syncId = sg.mSyncId;
+ mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */);
+ mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
+ for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
+ final ActivityRecord ar = mConfigAtEndActivities.get(i);
+ mSyncEngine.addToSyncSet(syncId, ar);
+ ar.resumeConfigurationDispatch();
+ }
+ mSyncEngine.setReady(syncId);
+ }
+
@Nullable
private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) {
if (mTransientLaunches == null) return null;
@@ -1546,6 +1635,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mState == STATE_ABORT) {
mController.onAbort(this);
+ if (mConfigAtEndActivities != null) {
+ for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
+ mConfigAtEndActivities.get(i).resumeConfigurationDispatch();
+ }
+ mConfigAtEndActivities = null;
+ }
primaryDisplay.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
@@ -2291,6 +2386,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
} else {
parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
}
+ final ActivityRecord ar = targetChange.mContainer.asActivityRecord();
+ if ((ar != null && ar.isConfigurationDispatchPaused())
+ || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) {
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+ }
}
}
@@ -2940,6 +3040,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
/** Whether this change's container moved to the top. */
private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
+ /** Whether this change contains config-at-end members. */
+ private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40;
+
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
FLAG_SEAMLESS_ROTATION,
@@ -2947,7 +3050,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
FLAG_ABOVE_TRANSIENT_LAUNCH,
FLAG_CHANGE_NO_ANIMATION,
FLAG_CHANGE_YES_ANIMATION,
- FLAG_CHANGE_MOVED_TO_TOP
+ FLAG_CHANGE_MOVED_TO_TOP,
+ FLAG_CHANGE_CONFIG_AT_END
})
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
@@ -3095,6 +3199,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
flags |= FLAG_IS_VOICE_INTERACTION;
}
flags |= record.mTransitionChangeFlags;
+ if (record.isConfigurationDispatchPaused()) {
+ flags |= FLAG_CONFIG_AT_END;
+ }
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
@@ -3140,6 +3247,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
flags |= FLAG_MOVED_TO_TOP;
}
+ if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) {
+ flags |= FLAG_CONFIG_AT_END;
+ }
return flags;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3f889c01bafb..90d3276770b2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -583,8 +583,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
- Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
- t.getChanges().entrySet().iterator();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries;
+ if (transition != null) {
+ // Mark any config-at-end containers before applying config changes so that
+ // the config changes don't dispatch to client.
+ entries = t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ if (!entry.getValue().getConfigAtTransitionEnd()) continue;
+ final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+ if (wc == null || !wc.isAttached()) continue;
+ transition.setConfigAtEnd(wc);
+ }
+ }
+ entries = t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
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 7d8eb90ea79c..ce890f6d8751 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -33,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -2540,6 +2541,36 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testConfigAtEnd() {
+ final TransitionController controller = mDisplayContent.mTransitionController;
+ Transition transit = createTestTransition(TRANSIT_CHANGE, controller);
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ final Task task = createTask(mDisplayContent);
+ final Rect taskBounds = new Rect(0, 0, 200, 300);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.setVisibleRequested(true);
+ activity.setVisible(true);
+
+ controller.moveToCollecting(transit);
+ transit.collect(task);
+ transit.setConfigAtEnd(task);
+ task.getRequestedOverrideConfiguration().windowConfiguration.setBounds(
+ new Rect(10, 10, 200, 300));
+ task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration());
+
+ controller.requestStartTransition(transit, task, null, null);
+ player.start();
+ assertTrue(activity.isConfigurationDispatchPaused());
+ // config-at-end flag must propagate up to task if activity was promoted.
+ assertTrue(player.mLastReady.getChange(
+ task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
+ player.finish();
+ assertFalse(activity.isConfigurationDispatchPaused());
+ }
+
+ @Test
public void testReadyTrackerBasics() {
final TransitionController controller = new TestTransitionController(
mock(ActivityTaskManagerService.class));