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

Commit 02192c22 authored by Evan Rosky's avatar Evan Rosky Committed by Automerger Merge Worker
Browse files

Merge "Support transitions waiting during collection in parallel" into udc-dev am: 1e806e1f

parents 31252ad9 1e806e1f
Loading
Loading
Loading
Loading
+125 −20
Original line number Diff line number Diff line
@@ -107,6 +107,11 @@ import java.util.function.Predicate;
 * Represents a logical transition. This keeps track of all the changes associated with a logical
 * WM state -> state transition.
 * @see TransitionController
 *
 * In addition to tracking individual container changes, this also tracks ordering-changes (just
 * on-top for now). However, since order is a "global" property, the mechanics of order-change
 * detection/reporting is non-trivial when transitions are collecting in parallel. See
 * {@link #collectOrderChanges} for more details.
 */
class Transition implements BLASTSyncEngine.TransactionReadyListener {
    private static final String TAG = "Transition";
@@ -191,6 +196,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     */
    private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();

    /**
     * The (non alwaysOnTop) tasks which were on-top of their display when this transition became
     * ready (via setReady, not animation-ready).
     */
    private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>();

    /**
     * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
     * the transition animation.
@@ -244,6 +255,36 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     */
    boolean mIsPlayerEnabled = true;

    /** This transition doesn't run in parallel. */
    static final int PARALLEL_TYPE_NONE = 0;

    /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
    static final int PARALLEL_TYPE_MUTUAL = 1;

    @IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
            PARALLEL_TYPE_NONE,
            PARALLEL_TYPE_MUTUAL
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ParallelType {}

    /**
     * What category of parallel-collect support this transition has. The value of this is used
     * by {@link TransitionController} to determine which transitions can collect in parallel. If
     * a transition can collect in parallel, it means that it will start collecting as soon as the
     * prior collecting transition is {@link #isPopulated}. This is a shortcut for supporting
     * a couple specific situations before we have full-fledged support for parallel transitions.
     */
    @ParallelType int mParallelCollectType = PARALLEL_TYPE_NONE;

    /**
     * A "Track" is a set of animations which must cooperate with each other to play smoothly. If
     * animations can play independently of each other, then they can be in different tracks. If
     * a transition must cooperate with transitions in >1 other track, then it must be marked
     * FLAG_SYNC and it will end-up flushing all animations before it starts.
     */
    int mAnimationTrack = 0;

    Transition(@TransitionType int type, @TransitionFlags int flags,
            TransitionController controller, BLASTSyncEngine syncEngine) {
        mType = type;
@@ -447,7 +488,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            throw new IllegalStateException("Attempting to re-use a transition");
        }
        mState = STATE_COLLECTING;
        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, false /* parallel */);
        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
                mParallelCollectType != PARALLEL_TYPE_NONE);
        mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);

        mLogger.mSyncId = mSyncId;
@@ -718,8 +760,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        final boolean ready = mReadyTracker.allReady();
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                "Set transition ready=%b %d", ready, mSyncId);
        mSyncEngine.setReady(mSyncId, ready);
        if (ready) mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
        boolean changed = mSyncEngine.setReady(mSyncId, ready);
        if (changed && ready) {
            mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
            mOnTopTasksAtReady.clear();
            for (int i = 0; i < mTargetDisplays.size(); ++i) {
                addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
            }
            mController.onTransitionPopulated(this);
        }
    }

    /**
@@ -737,6 +786,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        return mReadyTracker.allReady();
    }

    /** This transition has all of its expected participants. */
    boolean isPopulated() {
        return mState >= STATE_STARTED && mReadyTracker.allReady();
    }

    /**
     * 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
@@ -1211,7 +1265,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                : mController.mAtm.mRootWindowContainer.getDefaultDisplay();

        if (mState == STATE_ABORT) {
            mController.abort(this);
            mController.onAbort(this);
            primaryDisplay.getPendingTransaction().merge(transaction);
            mSyncId = -1;
            mOverrideOptions = null;
@@ -1222,13 +1276,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        mState = STATE_PLAYING;
        mStartTransaction = transaction;
        mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
        mController.moveToPlaying(this);

        // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect.
        if (primaryDisplay.isKeyguardLocked()) {
            mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
        }
        collectOrderChanges();

        // This is the only (or last) transition that is collecting, so we need to report any
        // leftover order changes.
        collectOrderChanges(mController.mWaitingTransitions.isEmpty());

        // Resolve the animating targets from the participants.
        mTargets = calculateTargets(mParticipants, mChanges);
@@ -1236,6 +1292,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
        final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
        info.setDebugId(mSyncId);
        mController.assignTrack(this, info);

        mController.moveToPlaying(this);

        // Repopulate the displays based on the resolved targets.
        mTargetDisplays.clear();
@@ -1383,17 +1442,58 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        info.releaseAnimSurfaces();
    }

    /** Collect tasks which moved-to-top but didn't change otherwise. */
    /**
     * Collect tasks which moved-to-top as part of this transition. This also updates the
     * controller's latest-reported when relevant.
     *
     * This is a non-trivial operation because transition can collect in parallel; however, it can
     * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still
     * globally serial; so, we can build some reasonable rules around it.
     *
     * First, we record the "start" on-top state (to compare against). Then, when this becomes
     * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state
     * -- the idea here is that upon "allReady", all the actual WM changes should be done and we
     * are now just waiting for window content to become ready (finish drawing).
     *
     * Then, in this function (during onTransactionReady), we compare the two orders and include
     * any changes to the order in the reported transition-info. Unfortunately, because of parallel
     * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a
     * global "latest reported order" in TransitionController and use that to make decisions.
     */
    @VisibleForTesting
    void collectOrderChanges() {
    void collectOrderChanges(boolean reportCurrent) {
        if (mOnTopTasksStart.isEmpty()) return;
        final ArrayList<Task> onTopTasksEnd = new ArrayList<>();
        for (int i = 0; i < mTargetDisplays.size(); ++i) {
            addOnTopTasks(mTargetDisplays.get(i), onTopTasksEnd);
        boolean includesOrderChange = false;
        for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) {
            final Task task = mOnTopTasksAtReady.get(i);
            if (mOnTopTasksStart.contains(task)) continue;
            includesOrderChange = true;
            break;
        }
        for (int i = 0; i < onTopTasksEnd.size(); ++i) {
        if (!includesOrderChange && !reportCurrent) {
            // This transition doesn't include an order change, so if it isn't required to report
            // the current focus (eg. it's the last of a cluster of transitions), then don't
            // report.
            return;
        }
        // The transition included an order change, but it may not be up-to-date, so grab the
        // latest state and compare with the last reported state (or our start state if no
        // reported state exists).
        ArrayList<Task> onTopTasksEnd = new ArrayList<>();
        for (int d = 0; d < mTargetDisplays.size(); ++d) {
            addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
            final int displayId = mTargetDisplays.get(d).mDisplayId;
            ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId);
            for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
                final Task task = onTopTasksEnd.get(i);
                if (task.getDisplayId() != displayId) continue;
                // If it didn't change since last report, don't report
                if (reportedOnTop == null) {
                    if (mOnTopTasksStart.contains(task)) continue;
                } else if (reportedOnTop.contains(task)) {
                    continue;
                }
                // Need to report it.
                mParticipants.add(task);
                int changeIdx = mChanges.indexOfKey(task);
                if (changeIdx < 0) {
@@ -1402,6 +1502,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                }
                mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
            }
            // Swap in the latest on-top tasks.
            mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd);
            onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>();
            onTopTasksEnd.clear();
        }
    }

    private void postCleanupOnFailure() {
+199 −16
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
@@ -60,7 +61,34 @@ import java.util.ArrayList;
import java.util.function.LongConsumer;

/**
 * Handles all the aspects of recording and synchronizing transitions.
 * Handles all the aspects of recording (collecting) and synchronizing transitions. This is only
 * concerned with the WM changes. The actual animations are handled by the Player.
 *
 * Currently, only 1 transition can be the primary "collector" at a time. This is because WM changes
 * are still performed in a "global" manner. However, collecting can actually be broken into
 * two phases:
 *    1. Actually making WM changes and recording the participating containers.
 *    2. Waiting for the participating containers to become ready (eg. redrawing content).
 * Because (2) takes most of the time AND doesn't change WM, we can actually have multiple
 * transitions in phase (2) concurrently with one in phase (1). We refer to this arrangement as
 * "parallel" collection even though there is still only ever 1 transition actually able to gain
 * participants.
 *
 * Parallel collection happens when the "primary collector" has finished "setup" (phase 1) and is
 * just waiting. At this point, another transition can start collecting. When this happens, the
 * first transition is moved to a "waiting" list and the new transition becomes the "primary
 * collector". If at any time, the "primary collector" moves to playing before one of the waiting
 * transitions, then the first waiting transition will move back to being the "primary collector".
 * This maintains the "global"-like abstraction that the rest of WM currently expects.
 *
 * When a transition move-to-playing, we check it against all other playing transitions. If it
 * doesn't overlap with them, it can also animate in parallel. In this case it will be assigned a
 * new "track". "tracks" are a way to communicate to the player about which transitions need to be
 * played serially with each-other. So, if we find that a transition overlaps with other transitions
 * in one track, the transition will be assigned to that track. If, however, the transition overlaps
 * with transition in >1 track, we will actually just mark it as SYNC meaning it can't actually
 * play until all prior transition animations finish. This is heavy-handed because it is a fallback
 * situation and supporting something fancier would be unnecessarily complicated.
 */
class TransitionController {
    private static final String TAG = "TransitionController";
@@ -109,6 +137,7 @@ class TransitionController {
     * removed from this list.
     */
    private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
    int mTrackCount = 0;

    /** The currently finishing transition. */
    Transition mFinishingTransition;
@@ -143,9 +172,25 @@ class TransitionController {

    private final ArrayList<QueuedTransition> mQueuedTransitions = new ArrayList<>();

    /** The transition currently being constructed (collecting participants). */
    /**
     * The transition currently being constructed (collecting participants). Unless interrupted,
     * all WM changes will go into this.
     */
    private Transition mCollectingTransition = null;

    /**
     * The transitions that are complete but still waiting for participants to become ready
     */
    final ArrayList<Transition> mWaitingTransitions = new ArrayList<>();

    /**
     * The (non alwaysOnTop) tasks which were reported as on-top of their display most recently
     * within a cluster of simultaneous transitions. If tasks are nested, all the tasks that are
     * parents of the on-top task are also included. This is used to decide which transitions
     * report which on-top changes.
     */
    final SparseArray<ArrayList<Task>> mLatestOnTopTasksReported = new SparseArray<>();

    /**
     * `true` when building surface layer order for the finish transaction. We want to prevent
     * wm from touching z-order of surfaces during transitions, but we still need to be able to
@@ -199,6 +244,11 @@ class TransitionController {
            mPlayingTransitions.get(i).cleanUpOnFailure();
        }
        mPlayingTransitions.clear();
        // Clean up waiting transitions first since they technically started first.
        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
            mWaitingTransitions.get(i).abort();
        }
        mWaitingTransitions.clear();
        if (mCollectingTransition != null) {
            mCollectingTransition.abort();
        }
@@ -223,8 +273,9 @@ class TransitionController {
            throw new IllegalStateException("Shell Transitions not enabled");
        }
        if (mCollectingTransition != null) {
            throw new IllegalStateException("Simultaneous transition collection not supported"
                    + " yet. Use {@link #createPendingTransition} for explicit queueing.");
            throw new IllegalStateException("Trying to directly start transition collection while "
                    + " collection is already ongoing. Use {@link #startCollectOrQueue} if"
                    + " possible.");
        }
        Transition transit = new Transition(type, flags, this, mSyncEngine);
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
@@ -318,7 +369,12 @@ class TransitionController {
     *                      This is {@code false} once a transition is playing.
     */
    boolean isCollecting(@NonNull WindowContainer wc) {
        return mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc);
        if (mCollectingTransition == null) return false;
        if (mCollectingTransition.mParticipants.contains(wc)) return true;
        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
            if (mWaitingTransitions.get(i).mParticipants.contains(wc)) return true;
        }
        return false;
    }

    /**
@@ -327,7 +383,11 @@ class TransitionController {
     */
    boolean inCollectingTransition(@NonNull WindowContainer wc) {
        if (!isCollecting()) return false;
        return mCollectingTransition.isInTransition(wc);
        if (mCollectingTransition.isInTransition(wc)) return true;
        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
            if (mWaitingTransitions.get(i).isInTransition(wc)) return true;
        }
        return false;
    }

    /**
@@ -369,6 +429,9 @@ class TransitionController {
        if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
            return true;
        }
        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
            if (mWaitingTransitions.get(i).isOnDisplay(dc)) return true;
        }
        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
            if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true;
        }
@@ -379,6 +442,9 @@ class TransitionController {
        if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
            return true;
        }
        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
        }
        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
            if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
        }
@@ -754,6 +820,8 @@ class TransitionController {
        mRunningLock.doNotifyLocked();
        // Run state-validation checks when no transitions are active anymore.
        if (!inTransition()) {
            // Can reset track-count now that everything is idle.
            mTrackCount = 0;
            validateStates();
        }
    }
@@ -771,12 +839,39 @@ class TransitionController {
        mStateValidators.clear();
    }

    /**
     * Called when the transition has a complete set of participants for its operation. In other
     * words, it is when the transition is "ready" but is still waiting for participants to draw.
     */
    void onTransitionPopulated(Transition transition) {
        tryStartCollectFromQueue();
    }

    private boolean canStartCollectingNow(Transition queued) {
        if (mCollectingTransition == null) return true;
        // Population (collect until ready) is still serialized, so always wait for that.
        if (!mCollectingTransition.isPopulated()) return false;
        // Check if queued *can* be independent with all collecting/waiting transitions.
        if (!getCanBeIndependent(mCollectingTransition, queued)) return false;
        for (int i = 0; i < mWaitingTransitions.size(); ++i) {
            if (!getCanBeIndependent(mWaitingTransitions.get(i), queued)) return false;
        }
        return true;
    }

    void tryStartCollectFromQueue() {
        if (mQueuedTransitions.isEmpty()) return;
        // Only need to try the next one since, even when transition can collect in parallel,
        // they still need to serialize on readiness.
        final QueuedTransition queued = mQueuedTransitions.get(0);
        if (mCollectingTransition != null || mSyncEngine.hasActiveSync()) {
        if (mCollectingTransition != null) {
            // If it's a legacy sync, then it needs to wait until there is no collecting transition.
            if (queued.mTransition == null) return;
            if (!canStartCollectingNow(queued.mTransition)) return;
            mWaitingTransitions.add(mCollectingTransition);
            mCollectingTransition = null;
        } else if (mSyncEngine.hasActiveSync()) {
            // A legacy transition is on-going, so we must wait.
            return;
        }
        mQueuedTransitions.remove(0);
@@ -797,16 +892,81 @@ class TransitionController {
    }

    void moveToPlaying(Transition transition) {
        if (transition != mCollectingTransition) {
            throw new IllegalStateException("Trying to move non-collecting transition to playing");
        }
        if (transition == mCollectingTransition) {
            mCollectingTransition = null;
            if (!mWaitingTransitions.isEmpty()) {
                mCollectingTransition = mWaitingTransitions.remove(0);
            }
            if (mCollectingTransition == null) {
                // nothing collecting anymore, so clear order records.
                mLatestOnTopTasksReported.clear();
            }
        } else {
            if (!mWaitingTransitions.remove(transition)) {
                throw new IllegalStateException("Trying to move non-collecting transition to"
                        + "playing " + transition.getSyncId());
            }
        }
        mPlayingTransitions.add(transition);
        updateRunningRemoteAnimation(transition, true /* isPlaying */);
        mTransitionTracer.logState(transition);
        // Sync engine should become idle after this, so the idle listener will check the queue.
    }

    /**
     * Checks if the `queued` transition has the potential to run independently of the
     * `collecting` transition. It may still ultimately block in sync-engine or become dependent
     * in {@link #getIsIndependent} later.
     */
    boolean getCanBeIndependent(Transition collecting, Transition queued) {
        if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                && collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
            return true;
        }
        return false;
    }

    /**
     * Checks if `incoming` transition can run independently of `running` transition assuming that
     * `running` is playing based on its current state.
     */
    static boolean getIsIndependent(Transition running, Transition incoming) {
        if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
                && incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
            return true;
        }
        return false;
    }

    void assignTrack(Transition transition, TransitionInfo info) {
        int track = -1;
        boolean sync = false;
        for (int i = 0; i < mPlayingTransitions.size(); ++i) {
            // ignore ourself obviously
            if (mPlayingTransitions.get(i) == transition) continue;
            if (getIsIndependent(mPlayingTransitions.get(i), transition)) continue;
            if (track >= 0) {
                // At this point, transition overlaps with multiple tracks, so just wait for
                // everything
                sync = true;
                break;
            }
            track = mPlayingTransitions.get(i).mAnimationTrack;
        }
        if (sync) {
            track = 0;
        }
        if (track < 0) {
            // Didn't overlap with anything, so give it its own track
            track = mTrackCount;
        }
        if (sync) {
            info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
        }
        info.setTrack(track);
        mTrackCount = Math.max(mTrackCount, track + 1);
    }

    void updateAnimatingState(SurfaceControl.Transaction t) {
        final boolean animatingState = !mPlayingTransitions.isEmpty()
                    || (mCollectingTransition != null && mCollectingTransition.isStarted());
@@ -842,14 +1002,27 @@ class TransitionController {
        mRemotePlayer.update(delegate, isPlaying, true /* predict */);
    }

    void abort(Transition transition) {
    /** Called when a transition is aborted. This should only be called by {@link Transition} */
    void onAbort(Transition transition) {
        if (transition != mCollectingTransition) {
            throw new IllegalStateException("Too late to abort.");
            int waitingIdx = mWaitingTransitions.indexOf(transition);
            if (waitingIdx < 0) {
                throw new IllegalStateException("Too late for abort.");
            }
        transition.abort();
            mWaitingTransitions.remove(waitingIdx);
        } else {
            mCollectingTransition = null;
            if (!mWaitingTransitions.isEmpty()) {
                mCollectingTransition = mWaitingTransitions.remove(0);
            }
            if (mCollectingTransition == null) {
                // nothing collecting anymore, so clear order records.
                mLatestOnTopTasksReported.clear();
            }
        }
        mTransitionTracer.logState(transition);
        // abort will call through the normal finish paths and thus check the queue.
        // This is called during Transition.abort whose codepath will eventually check the queue
        // via sync-engine idle.
    }

    /**
@@ -956,7 +1129,17 @@ class TransitionController {
            return false;
        }
        if (mSyncEngine.hasActiveSync()) {
            if (!isCollecting()) {
            if (isCollecting()) {
                // Check if we can run in parallel here.
                if (canStartCollectingNow(transit)) {
                    // start running in parallel.
                    mWaitingTransitions.add(mCollectingTransition);
                    mCollectingTransition = null;
                    moveToCollecting(transit);
                    onStartCollect.onCollectStarted(false /* deferred */);
                    return true;
                }
            } else {
                Slog.w(TAG, "Ongoing Sync outside of transition.");
            }
            queueTransition(transit, onStartCollect);
+279 −1

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -382,7 +382,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
        assertTrue(wallpaperWindow.isVisible());
        assertTrue(token.isVisibleRequested());
        assertTrue(token.isVisible());
        mWm.mAtmService.getTransitionController().abort(transit);
        transit.abort();

        // In a transition, setting invisible should ONLY set requestedVisible false; otherwise
        // wallpaper should remain "visible" until transition is over.