Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddcfd119 authored by Evan Rosky's avatar Evan Rosky
Browse files

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
parent ebdb4991
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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 {}
+30 −0
Original line number Diff line number Diff line
@@ -913,6 +913,23 @@ public final class WindowContainerTransaction implements Parcelable {
        return this;
    }

    /**
     * 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
@@ -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);
+157 −47
Original line number Diff line number Diff line
@@ -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,23 +962,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
    }

    /**
     * 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.
     * 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 buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
    private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target,
            SurfaceControl targetLeash) {
        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
@@ -963,10 +980,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            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
@@ -974,7 +988,34 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        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) {
        // 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) 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;
        }

+15 −2
Original line number Diff line number Diff line
@@ -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());
+31 −0
Original line number Diff line number Diff line
@@ -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;
@@ -2539,6 +2540,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(