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

Commit 243c74b1 authored by Garfield Tan's avatar Garfield Tan
Browse files

Add an observer class for transition lifecycles

This will be used to transfer window decorations over transitions,
regardless of the actual handler of these transitions.

Bug: 241975249
Test: atest ShellTransitionTests
Change-Id: I86486ec5af5057c8b22ccd7e4929627667672049
parent fcfe5eec
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
@@ -119,6 +119,8 @@ public class Transitions implements RemoteCallable<Transitions> {
    /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();

    private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();

    /** List of {@link Runnable} instances to run when the last active transition has finished.  */
    private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();

@@ -242,6 +244,16 @@ public class Transitions implements RemoteCallable<Transitions> {
        mRemoteTransitionHandler.removeFiltered(remoteTransition);
    }

    /** Registers an observer on the lifecycle of transitions. */
    public void registerObserver(@NonNull TransitionObserver observer) {
        mObservers.add(observer);
    }

    /** Unregisters the observer. */
    public void unregisterObserver(@NonNull TransitionObserver observer) {
        mObservers.remove(observer);
    }

    /** Boosts the process priority of remote animation player. */
    public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
        if (appThread == null) return;
@@ -407,6 +419,11 @@ public class Transitions implements RemoteCallable<Transitions> {
                    + Arrays.toString(mActiveTransitions.stream().map(
                            activeTransition -> activeTransition.mToken).toArray()));
        }

        for (int i = 0; i < mObservers.size(); ++i) {
            mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
        }

        if (!info.getRootLeash().isValid()) {
            // Invalid root-leash implies that the transition is empty/no-op, so just do
            // housekeeping and return.
@@ -474,6 +491,10 @@ public class Transitions implements RemoteCallable<Transitions> {
    }

    private void playTransition(@NonNull ActiveTransition active) {
        for (int i = 0; i < mObservers.size(); ++i) {
            mObservers.get(i).onTransitionStarting(active.mToken);
        }

        setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);

        // If a handler already chose to run this animation, try delegating to it first.
@@ -546,6 +567,10 @@ public class Transitions implements RemoteCallable<Transitions> {
                active.mHandler.onTransitionConsumed(
                        active.mToken, abort, abort ? null : active.mFinishT);
            }
            for (int i = 0; i < mObservers.size(); ++i) {
                mObservers.get(i).onTransitionMerged(
                        active.mToken, mActiveTransitions.get(0).mToken);
            }
            return;
        }
        final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -555,6 +580,9 @@ public class Transitions implements RemoteCallable<Transitions> {
            active.mHandler.onTransitionConsumed(
                    transition, true /* aborted */, null /* finishTransaction */);
        }
        for (int i = 0; i < mObservers.size(); ++i) {
            mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
        }
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "Transition animation finished (abort=%b), notifying core %s", abort, transition);
        // Merge all relevant transactions together
@@ -593,6 +621,9 @@ public class Transitions implements RemoteCallable<Transitions> {
                        transition, true /* aborted */, null /* finishTransaction */);
            }
            mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
            for (int i = 0; i < mObservers.size(); ++i) {
                mObservers.get(i).onTransitionFinished(active.mToken, true);
            }
        }
        if (mActiveTransitions.size() <= activeIdx) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -792,6 +823,52 @@ public class Transitions implements RemoteCallable<Transitions> {
        default void setAnimScaleSetting(float scale) {}
    }

    /**
     * Interface for something that needs to know the lifecycle of some transitions, but never
     * handles any transition by itself.
     */
    public interface TransitionObserver {
        /**
         * Called when the transition is ready to play. It may later be merged into other
         * transitions. Note this doesn't mean this transition will be played anytime soon.
         *
         * @param transition the unique token of this transition
         * @param startTransaction the transaction given to the handler to be applied before the
         *                         transition animation. This will be applied when the transition
         *                         handler that handles this transition starts the transition.
         * @param finishTransaction the transaction given to the handler to be applied after the
         *                          transition animation. The Transition system will apply it when
         *                          finishCallback is called by the transition handler.
         */
        void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction startTransaction,
                @NonNull SurfaceControl.Transaction finishTransaction);

        /**
         * Called when the transition is starting to play. It isn't called for merged transitions.
         *
         * @param transition the unique token of this transition
         */
        void onTransitionStarting(@NonNull IBinder transition);

        /**
         * Called when a transition is merged into another transition. There won't be any following
         * lifecycle calls for the merged transition.
         *
         * @param merged the unique token of the transition that's merged to another one
         * @param playing the unique token of the transition that accepts the merge
         */
        void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);

        /**
         * Called when the transition is finished. This isn't called for merged transitions.
         *
         * @param transition the unique token of this transition
         * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
         */
        void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
    }

    @BinderThread
    private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
        @Override
+201 −0
Original line number Diff line number Diff line
@@ -43,11 +43,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -89,6 +91,7 @@ import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;

import java.util.ArrayList;

@@ -688,6 +691,204 @@ public class ShellTransitionTests extends ShellTestCase {
        verify(runnable4, times(1)).run();
    }

    @Test
    public void testObserverLifecycle_basicTransitionFlow() {
        Transitions transitions = createTestTransitions();
        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
        transitions.registerObserver(observer);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        IBinder transitToken = new Binder();
        transitions.requestStartTransition(transitToken,
                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken, info, startT, finishT);

        InOrder observerOrder = inOrder(observer);
        observerOrder.verify(observer).onTransitionReady(transitToken, info, startT, finishT);
        observerOrder.verify(observer).onTransitionStarting(transitToken);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken), anyBoolean());
        mDefaultHandler.finishAll();
        mMainExecutor.flushAll();
        verify(observer).onTransitionFinished(transitToken, false);
    }

    @Test
    public void testObserverLifecycle_queueing() {
        Transitions transitions = createTestTransitions();
        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
        transitions.registerObserver(observer);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        IBinder transitToken1 = new Binder();
        transitions.requestStartTransition(transitToken1,
                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
        verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);

        IBinder transitToken2 = new Binder();
        transitions.requestStartTransition(transitToken2,
                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
        verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
        verify(observer, times(0)).onTransitionStarting(transitToken2);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());

        mDefaultHandler.finishAll();
        mMainExecutor.flushAll();
        // first transition finished
        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
        verify(observer, times(1)).onTransitionStarting(transitToken2);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());

        mDefaultHandler.finishAll();
        mMainExecutor.flushAll();
        verify(observer, times(1)).onTransitionFinished(transitToken2, false);
    }


    @Test
    public void testObserverLifecycle_merging() {
        Transitions transitions = createTestTransitions();
        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
        transitions.registerObserver(observer);
        mDefaultHandler.setSimulateMerge(true);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        IBinder transitToken1 = new Binder();
        transitions.requestStartTransition(transitToken1,
                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);

        IBinder transitToken2 = new Binder();
        transitions.requestStartTransition(transitToken2,
                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);

        InOrder observerOrder = inOrder(observer);
        observerOrder.verify(observer).onTransitionReady(transitToken2, info2, startT2, finishT2);
        observerOrder.verify(observer).onTransitionMerged(transitToken2, transitToken1);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());

        mDefaultHandler.finishAll();
        mMainExecutor.flushAll();
        // transition + merged all finished.
        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
        // Merged transition won't receive any lifecycle calls beyond ready
        verify(observer, times(0)).onTransitionStarting(transitToken2);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
    }

    @Test
    public void testObserverLifecycle_mergingAfterQueueing() {
        Transitions transitions = createTestTransitions();
        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
        transitions.registerObserver(observer);
        mDefaultHandler.setSimulateMerge(true);
        transitions.replaceDefaultHandlerForTest(mDefaultHandler);

        // Make a test handler that only responds to multi-window triggers AND only animates
        // Change transitions.
        final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
        TestTransitionHandler testHandler = new TestTransitionHandler() {
            @Override
            public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                    @NonNull SurfaceControl.Transaction startTransaction,
                    @NonNull SurfaceControl.Transaction finishTransaction,
                    @NonNull Transitions.TransitionFinishCallback finishCallback) {
                for (TransitionInfo.Change chg : info.getChanges()) {
                    if (chg.getMode() == TRANSIT_CHANGE) {
                        return super.startAnimation(transition, info, startTransaction,
                                finishTransaction, finishCallback);
                    }
                }
                return false;
            }

            @Nullable
            @Override
            public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
                    @NonNull TransitionRequestInfo request) {
                final RunningTaskInfo task = request.getTriggerTask();
                return (task != null && task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
                        ? handlerWCT : null;
            }
        };
        transitions.addHandler(testHandler);

        // Use test handler to play an animation
        IBinder transitToken1 = new Binder();
        RunningTaskInfo mwTaskInfo =
                createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
        transitions.requestStartTransition(transitToken1,
                new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
        TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
                .addChange(TRANSIT_CHANGE).build();
        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken1, change, startT1, finishT1);

        // Request the second transition that should be handled by the default handler
        IBinder transitToken2 = new Binder();
        TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
        transitions.requestStartTransition(transitToken2,
                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
        verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
        verify(observer, times(0)).onTransitionStarting(transitToken2);

        // Request the third transition that should be merged into the second one
        IBinder transitToken3 = new Binder();
        transitions.requestStartTransition(transitToken3,
                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
        SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
        SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
        transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
        verify(observer, times(0)).onTransitionStarting(transitToken2);
        verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
        verify(observer, times(0)).onTransitionStarting(transitToken3);

        testHandler.finishAll();
        mMainExecutor.flushAll();

        verify(observer).onTransitionFinished(transitToken1, false);

        mDefaultHandler.finishAll();
        mMainExecutor.flushAll();

        InOrder observerOrder = inOrder(observer);
        observerOrder.verify(observer).onTransitionStarting(transitToken2);
        observerOrder.verify(observer).onTransitionMerged(transitToken3, transitToken2);
        observerOrder.verify(observer).onTransitionFinished(transitToken2, false);

        // Merged transition won't receive any lifecycle calls beyond ready
        verify(observer, times(0)).onTransitionStarting(transitToken3);
        verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
    }

    class TransitionInfoBuilder {
        final TransitionInfo mInfo;