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

Commit 3c1ea525 authored by Jerry Chang's avatar Jerry Chang
Browse files

Consolidate recent animation in split screen with shell-transition

Make sure to delegate recent transition to launcher after split screen
activated.

Bug: 206487881
Test: manual check swipe split-pair to overview panel and swipe to home
      behavior
Change-Id: I1177b1efdd7a45e8f9ec162b2155dbea1660e1af
parent a805c7e5
Loading
Loading
Loading
Loading
+40 −13
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
@@ -66,28 +67,26 @@ class SplitScreenTransitions {

    DismissTransition mPendingDismiss = null;
    IBinder mPendingEnter = null;
    IBinder mPendingRecent = null;

    private IBinder mAnimatingTransition = null;
    private OneShotRemoteHandler mRemoteHandler = null;

    private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
        if (wct != null || wctCB != null) {
            throw new UnsupportedOperationException("finish transactions not supported yet.");
        }
        onFinish();
    };
    private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;

    /** Keeps track of currently running animations */
    private final ArrayList<Animator> mAnimations = new ArrayList<>();
    private final StageCoordinator mStageCoordinator;

    private Transitions.TransitionFinishCallback mFinishCallback = null;
    private SurfaceControl.Transaction mFinishTransaction;

    SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
            @NonNull Runnable onFinishCallback) {
            @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
        mTransactionPool = pool;
        mTransitions = transitions;
        mOnFinish = onFinishCallback;
        mStageCoordinator = stageCoordinator;
    }

    void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -166,7 +165,7 @@ class SplitScreenTransitions {
            }
        }
        t.apply();
        onFinish();
        onFinish(null /* wct */, null /* wctCB */);
    }

    /** Starts a transition to enter split with a remote transition animator. */
@@ -203,7 +202,26 @@ class SplitScreenTransitions {
        return transition;
    }

    void onFinish() {
    IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
            Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) {
        if (transition == null) {
            transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler);
        }
        mPendingRecent = transition;

        if (remoteTransition != null) {
            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
            mRemoteHandler = new OneShotRemoteHandler(
                    mTransitions.getMainExecutor(), remoteTransition);
            mRemoteHandler.setTransition(transition);
        }

        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
                        + " deduced Enter recent panel");
        return transition;
    }

    void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
        if (!mAnimations.isEmpty()) return;
        mOnFinish.run();
        if (mFinishTransaction != null) {
@@ -211,14 +229,23 @@ class SplitScreenTransitions {
            mTransactionPool.release(mFinishTransaction);
            mFinishTransaction = null;
        }
        mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
        if (mFinishCallback != null) {
            mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
            mFinishCallback = null;
        }
        if (mAnimatingTransition == mPendingEnter) {
            mPendingEnter = null;
        }
        if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
            mPendingDismiss = null;
        }
        if (mAnimatingTransition == mPendingRecent) {
            // If the wct is not null while finishing recent transition, it indicates it's not
            // returning to home and hence needing the wct to reorder tasks.
            final boolean toHome = wct == null;
            mStageCoordinator.finishRecentAnimation(toHome);
            mPendingRecent = null;
        }
        mAnimatingTransition = null;
    }

@@ -240,7 +267,7 @@ class SplitScreenTransitions {
            mTransactionPool.release(transaction);
            mTransitions.getMainExecutor().execute(() -> {
                mAnimations.remove(va);
                onFinish();
                onFinish(null /* wct */, null /* wctCB */);
            });
        };
        va.addListener(new Animator.AnimatorListener() {
@@ -288,7 +315,7 @@ class SplitScreenTransitions {
            mTransactionPool.release(transaction);
            mTransitions.getMainExecutor().execute(() -> {
                mAnimations.remove(va);
                onFinish();
                onFinish(null /* wct */, null /* wctCB */);
            });
        };
        va.addListener(new AnimatorListenerAdapter() {
+33 −9
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -236,7 +237,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
                new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
        mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                mOnTransitionAnimationComplete);
                mOnTransitionAnimationComplete, this);
        mDisplayController.addDisplayWindowListener(this);
        mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
        transitions.addHandler(this);
@@ -266,7 +267,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mRootTDAOrganizer.registerListener(displayId, this);
        mSplitLayout = splitLayout;
        mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                mOnTransitionAnimationComplete);
                mOnTransitionAnimationComplete, this);
        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
        mLogger = logger;
@@ -1271,13 +1272,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                final int activityType = triggerTask.getActivityType();
                if (activityType == ACTIVITY_TYPE_ASSISTANT) {
                    // We don't want assistant panel to dismiss split screen, so do nothing.
                } else if (activityType == ACTIVITY_TYPE_HOME
                        || activityType == ACTIVITY_TYPE_RECENTS) {
                    // Enter overview panel, so start recent transition.
                    mSplitTransitions.startRecentTransition(transition, out, this,
                            request.getRemoteTransition());
                } else {
                    // Going home or occluded by the other fullscreen task, so dismiss both.
                    // Occluded by the other fullscreen task, so dismiss both.
                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
                    final int exitReason = activityType == ACTIVITY_TYPE_HOME
                            ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN;
                    mSplitTransitions.startDismissTransition(transition, out, this,
                            STAGE_TYPE_UNDEFINED, exitReason);
                            STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
                }
            }
        } else {
@@ -1307,13 +1311,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        if (transition != mSplitTransitions.mPendingEnter && (
                mSplitTransitions.mPendingDismiss == null
        if (transition != mSplitTransitions.mPendingEnter
                && transition != mSplitTransitions.mPendingRecent
                && (mSplitTransitions.mPendingDismiss == null
                        || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
            // Not entering or exiting, so just do some house-keeping and validation.

            // If we're not in split-mode, just abort so something else can handle it.
            if (!isSplitScreenVisible()) return false;
            if (!mMainStage.isActive()) return false;

            for (int iC = 0; iC < info.getChanges().size(); ++iC) {
                final TransitionInfo.Change change = info.getChanges().get(iC);
@@ -1356,6 +1361,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        boolean shouldAnimate = true;
        if (mSplitTransitions.mPendingEnter == transition) {
            shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
        } else if (mSplitTransitions.mPendingRecent == transition) {
            shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
        } else if (mSplitTransitions.mPendingDismiss != null
                && mSplitTransitions.mPendingDismiss.mTransition == transition) {
            shouldAnimate = startPendingDismissAnimation(
@@ -1500,6 +1507,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        return true;
    }

    private boolean startPendingRecentAnimation(@NonNull IBinder transition,
            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
        setDividerVisibility(false, t);
        return true;
    }

    void finishRecentAnimation(boolean toHome) {
        if (toHome) {
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
            mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
                    STAGE_TYPE_UNDEFINED, EXIT_REASON_RETURN_HOME);
        } else {
            setDividerVisibility(true, null /* t */);
        }
    }

    private void addDividerBarToTransition(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, boolean show) {
        final SurfaceControl leash = mSplitLayout.getDividerLeash();
+3 −3
Original line number Diff line number Diff line
@@ -251,7 +251,7 @@ public class SplitTransitionTests extends ShellTestCase {
    }

    @Test
    public void testDismissToHome() {
    public void testEnterRecents() {
        enterSplit();

        ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -264,7 +264,7 @@ public class SplitTransitionTests extends ShellTestCase {
        IBinder transition = mock(IBinder.class);
        WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);

        assertTrue(containsSplitExit(result));
        assertTrue(result.isEmpty());

        // make sure we haven't made any local changes yet (need to wait until transition is ready)
        assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -284,7 +284,7 @@ public class SplitTransitionTests extends ShellTestCase {
                mock(SurfaceControl.Transaction.class),
                mock(SurfaceControl.Transaction.class),
                mock(Transitions.TransitionFinishCallback.class));
        assertFalse(mStageCoordinator.isSplitScreenVisible());
        assertTrue(mStageCoordinator.isSplitScreenVisible());
    }

    @Test
+12 −23
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.systemui.shared.system;

import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
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;
@@ -140,22 +139,11 @@ public class RemoteAnimationTargetCompat {
        // changes should be ordered top-to-bottom in z
        final int mode = change.getMode();

        // Don't move anything that isn't independent within its parents
        if (!TransitionInfo.isIndependent(change, info)) {
            if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
                t.show(leash);
                t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
            }
            return;
        }

        boolean hasParent = change.getParent() != null;

        if (!hasParent) {
        // Launcher animates leaf tasks directly, so always reparent all task leashes to root leash.
        t.reparent(leash, info.getRootLeash());
        t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
                change.getStartAbsBounds().top - info.getRootOffset().y);
        }

        t.show(leash);
        // Put all the OPEN/SHOW on top
        if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
@@ -266,14 +254,15 @@ public class RemoteAnimationTargetCompat {
            SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
        final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
        for (int i = 0; i < info.getChanges().size(); i++) {
            boolean changeIsWallpaper =
                    (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
            final TransitionInfo.Change change = info.getChanges().get(i);
            final boolean changeIsWallpaper =
                    (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
            if (wallpapers != changeIsWallpaper) continue;
            out.add(new RemoteAnimationTargetCompat(info.getChanges().get(i),
                    info.getChanges().size() - i, info, t));
            if (leashMap == null) continue;
            leashMap.put(info.getChanges().get(i).getLeash(),
                    out.get(out.size() - 1).leash);

            out.add(new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t));
            if (leashMap != null) {
                leashMap.put(change.getLeash(), out.get(out.size() - 1).leash);
            }
        }
        return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
    }