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

Commit 0d6ce4c7 authored by Jeremy Sim's avatar Jeremy Sim
Browse files

Add veil for swap animations when insets are in play

This CL adds the ability for SplitDecorManager to add a veil (with icon) when a swap animation is playing, if there are insets that would necessite a redraw at the end of the swap. (e.g. swapping vertically when there is an inset on the top/bottom).

It also removes the "screenshot" layer for split swaps, instead opting to achieve the same effect by removing window focus-ability during the animation.

Fixes: 291893598
Test: Visually confirmed in a variety of orientations, slowdowns, and input cases
Flag: EXEMPT bugfix
Change-Id: Ic34bf8c1f8eecb42fe4589b54f23558f2d0a5fe7
parent 19ed6347
Loading
Loading
Loading
Loading
+130 −25
Original line number Diff line number Diff line
@@ -23,7 +23,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.common.split.SplitScreenConstants.VEIL_DELAY_DURATION;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
    private final SurfaceSession mSurfaceSession;

    private Drawable mIcon;
    private ImageView mResizingIconView;
    private ImageView mVeilIconView;
    private SurfaceControlViewHost mViewHost;
    private SurfaceControl mHostLeash;
    private SurfaceControl mIconLeash;
@@ -83,13 +86,14 @@ public class SplitDecorManager extends WindowlessWindowManager {
    private SurfaceControl mScreenshot;

    private boolean mShown;
    private boolean mIsResizing;
    /** True if the task is going through some kind of transition (moving or changing size). */
    private boolean mIsCurrentlyChanging;
    /** The original bounds of the main task, captured at the beginning of a resize transition. */
    private final Rect mOldMainBounds = new Rect();
    /** The original bounds of the side task, captured at the beginning of a resize transition. */
    private final Rect mOldSideBounds = new Rect();
    /** The current bounds of the main task, mid-resize. */
    private final Rect mResizingBounds = new Rect();
    private final Rect mInstantaneousBounds = new Rect();
    private final Rect mTempRect = new Rect();
    private ValueAnimator mFadeAnimator;
    private ValueAnimator mScreenshotAnimator;
@@ -134,7 +138,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
        final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
                .inflate(R.layout.split_decor, null);
        mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
        mVeilIconView = rootLayout.findViewById(R.id.split_resizing_icon);

        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
@@ -191,28 +195,28 @@ public class SplitDecorManager extends WindowlessWindowManager {
        }
        mHostLeash = null;
        mIcon = null;
        mResizingIconView = null;
        mIsResizing = false;
        mVeilIconView = null;
        mIsCurrentlyChanging = false;
        mShown = false;
        mOldMainBounds.setEmpty();
        mOldSideBounds.setEmpty();
        mResizingBounds.setEmpty();
        mInstantaneousBounds.setEmpty();
    }

    /** Showing resizing hint. */
    public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
            Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
            boolean immediately) {
        if (mResizingIconView == null) {
        if (mVeilIconView == null) {
            return;
        }

        if (!mIsResizing) {
            mIsResizing = true;
        if (!mIsCurrentlyChanging) {
            mIsCurrentlyChanging = true;
            mOldMainBounds.set(newBounds);
            mOldSideBounds.set(sideBounds);
        }
        mResizingBounds.set(newBounds);
        mInstantaneousBounds.set(newBounds);
        mOffsetX = offsetX;
        mOffsetY = offsetY;

@@ -254,8 +258,8 @@ public class SplitDecorManager extends WindowlessWindowManager {

        if (mIcon == null && resizingTask.topActivityInfo != null) {
            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
            mResizingIconView.setImageDrawable(mIcon);
            mResizingIconView.setVisibility(View.VISIBLE);
            mVeilIconView.setImageDrawable(mIcon);
            mVeilIconView.setVisibility(View.VISIBLE);

            WindowManager.LayoutParams lp =
                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
@@ -275,7 +279,12 @@ public class SplitDecorManager extends WindowlessWindowManager {
                t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
                t.setVisibility(mIconLeash, showVeil);
            } else {
                startFadeAnimation(showVeil, false, null);
                startFadeAnimation(
                        showVeil,
                        false /* releaseSurface */,
                        null /* finishedCallback */,
                        false /* addDelay */
                );
            }
            mShown = showVeil;
        }
@@ -320,19 +329,19 @@ public class SplitDecorManager extends WindowlessWindowManager {
            mScreenshotAnimator.start();
        }

        if (mResizingIconView == null) {
        if (mVeilIconView == null) {
            if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                animFinishedCallback.accept(false);
            }
            return;
        }

        mIsResizing = false;
        mIsCurrentlyChanging = false;
        mOffsetX = 0;
        mOffsetY = 0;
        mOldMainBounds.setEmpty();
        mOldSideBounds.setEmpty();
        mResizingBounds.setEmpty();
        mInstantaneousBounds.setEmpty();
        if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
            if (!mShown) {
                // If fade-out animation is running, just add release callback to it.
@@ -356,7 +365,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
                if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
                    animFinishedCallback.accept(true);
                }
            });
            }, false /* addDelay */);
        } else {
            // Decor surface is hidden so release it directly.
            releaseDecor(t);
@@ -366,9 +375,94 @@ public class SplitDecorManager extends WindowlessWindowManager {
        }
    }

    /**
     * Called (on every frame) when two split apps are swapping, and a veil is needed.
     */
    public void drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask,
            Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind,
            SurfaceControl leash, float iconOffsetX, float iconOffsetY) {
        if (mVeilIconView == null) {
            return;
        }

        if (!mIsCurrentlyChanging) {
            mIsCurrentlyChanging = true;
        }

        mInstantaneousBounds.set(newBounds);
        mOffsetX = (int) iconOffsetX;
        mOffsetY = (int) iconOffsetY;

        t.setLayer(leash, isGoingBehind ? BEHIND_APP_VEIL_LAYER : FRONT_APP_VEIL_LAYER);

        if (!mShown) {
            if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
                // Cancel mFadeAnimator if it is running
                mFadeAnimator.cancel();
            }
        }

        if (mBackgroundLeash == null) {
            // Initialize background
            mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                    RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
            t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
                    .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
        }

        if (mIcon == null && resizingTask.topActivityInfo != null) {
            // Initialize icon
            mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
            mVeilIconView.setImageDrawable(mIcon);
            mVeilIconView.setVisibility(View.VISIBLE);

            WindowManager.LayoutParams lp =
                    (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
            lp.width = mIconSize;
            lp.height = mIconSize;
            mViewHost.relayout(lp);

            t.setLayer(mIconLeash, Integer.MAX_VALUE);
        }

        t.setPosition(mIconLeash,
                newBounds.width() / 2 - mIconSize / 2 - mOffsetX,
                newBounds.height() / 2 - mIconSize / 2 - mOffsetY);

        // If this is the first frame, we need to trigger the veil's fade-in animation.
        if (!mShown) {
            startFadeAnimation(
                    true /* show */,
                    false /* releaseSurface */,
                    null /* finishedCallball */,
                    false /* addDelay */
            );
            mShown = true;
        }
    }

    /** Called at the end of the swap animation. */
    public void fadeOutVeilAndCleanUp(SurfaceControl.Transaction t) {
        if (mVeilIconView == null) {
            return;
        }

        // Recenter icon
        t.setPosition(mIconLeash,
                mInstantaneousBounds.width() / 2f - mIconSize / 2f,
                mInstantaneousBounds.height() / 2f - mIconSize / 2f);

        mIsCurrentlyChanging = false;
        mOffsetX = 0;
        mOffsetY = 0;
        mInstantaneousBounds.setEmpty();

        fadeOutDecor(() -> {}, true /* addDelay */);
    }

    /** Screenshot host leash and attach on it if meet some conditions */
    public void screenshotIfNeeded(SurfaceControl.Transaction t) {
        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                mScreenshotAnimator.cancel();
            } else if (mScreenshot != null) {
@@ -386,7 +480,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
    public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
        if (screenshot == null || !screenshot.isValid()) return;

        if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
        if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
                mScreenshotAnimator.cancel();
            } else if (mScreenshot != null) {
@@ -401,24 +495,35 @@ public class SplitDecorManager extends WindowlessWindowManager {

    /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback
     * directly. */
    public void fadeOutDecor(Runnable finishedCallback) {
    public void fadeOutDecor(Runnable finishedCallback, boolean addDelay) {
        if (mShown) {
            // If previous animation is running, just cancel it.
            if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
                mFadeAnimator.cancel();
            }

            startFadeAnimation(false /* show */, true, finishedCallback);
            startFadeAnimation(
                    false /* show */, true /* releaseSurface */, finishedCallback, addDelay);
            mShown = false;
        } else {
            if (finishedCallback != null) finishedCallback.run();
        }
    }

    /**
     * Fades the veil in or out. Called at the first frame of a movement or resize when a veil is
     * needed (with show = true), and called again at the end (with show = false).
     * @param addDelay If true, adds a short delay before fading out to get the app behind the veil
     *                 time to redraw.
     */
    private void startFadeAnimation(boolean show, boolean releaseSurface,
            Runnable finishedCallback) {
            Runnable finishedCallback, boolean addDelay) {
        final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();

        mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
        if (addDelay) {
            mFadeAnimator.setStartDelay(VEIL_DELAY_DURATION);
        }
        mFadeAnimator.setDuration(FADE_DURATION);
        mFadeAnimator.addUpdateListener(valueAnimator-> {
            final float progress = (float) valueAnimator.getAnimatedValue();
@@ -481,8 +586,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
        }

        if (mIcon != null) {
            mResizingIconView.setVisibility(View.GONE);
            mResizingIconView.setImageDrawable(null);
            mVeilIconView.setVisibility(View.GONE);
            mVeilIconView.setImageDrawable(null);
            t.hide(mIconLeash);
            mIcon = null;
        }
+53 −19
Original line number Diff line number Diff line
@@ -70,12 +70,12 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;

import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.StageTaskListener;

import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -94,6 +94,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    private static final int FLING_ENTER_DURATION = 450;
    private static final int FLING_EXIT_DURATION = 450;

    // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
    // layers stay in order. Note: This does not affect any other layer numbering systems because
    // the layer system in WindowManager is local within sibling groups. So, for example, each
    // "veil layer" defined here actually has two sub-layers; and *their* layer values, which we set
    // in SplitDecorManager, are only important relative to each other.
    public static final int DIVIDER_LAYER = 0;
    public static final int FRONT_APP_VEIL_LAYER = DIVIDER_LAYER + 20;
    public static final int FRONT_APP_LAYER = DIVIDER_LAYER + 10;
    public static final int BEHIND_APP_VEIL_LAYER = DIVIDER_LAYER - 10;
    public static final int BEHIND_APP_LAYER = DIVIDER_LAYER - 20;

    // Animation specs for the swap animation
    private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
    private static final float SWAP_ANIMATION_SHRINK_DURATION = 83;
@@ -702,11 +713,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    }

    /** Switch both surface position with animation. */
    public void playSwapAnimation(SurfaceControl.Transaction t, SurfaceControl leash1,
            SurfaceControl leash2, Consumer<Rect> finishCallback) {
    public void playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage,
            StageTaskListener bottomRightStage, Consumer<Rect> finishCallback) {
        final Rect insets = getDisplayStableInsets(mContext);
        // If we have insets in the direction of the swap, the animation won't look correct because
        // window contents will shift and redraw again at the end. So we show a veil to hide that.
        insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
                mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
        final boolean shouldVeil =
                insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;

        final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
                mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
@@ -721,13 +736,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
        endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);

        ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), endBounds1,
                -insets.left, -insets.top, true /* roundCorners */, true /* addShrinkAnimation */);
        ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), endBounds2,
                insets.left, insets.top, true /* roundCorners */, false /* addShrinkAnimation */);
        ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
        ValueAnimator animator1 = moveSurface(t, topLeftStage, getRefBounds1(), endBounds1,
                -insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
                shouldVeil);
        ValueAnimator animator2 = moveSurface(t, bottomRightStage, getRefBounds2(), endBounds2,
                insets.left, insets.top, true /* roundCorners */, false /* isGoingBehind */,
                shouldVeil);
        ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
                endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
                false /* addShrinkAnimation */);
                false /* isGoingBehind */, false /* addVeil */);

        mSwapAnimator = new AnimatorSet();
        mSwapAnimator.playTogether(animator1, animator2, animator3);
@@ -737,11 +754,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
            public void onAnimationStart(Animator animation) {
                mInteractionJankMonitor.begin(getDividerLeash(),
                        mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);

                // The right/bottom app moves above the divider, the left/top app moves below.
                t.setLayer(leash1, Integer.MIN_VALUE);
                t.setLayer(getDividerLeash(), 0);
                t.setLayer(leash2, Integer.MAX_VALUE);
            }

            @Override
@@ -768,14 +780,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    /**
     * Animates a task leash across the screen. Currently used only for the swap animation.
     *
     * @param leash The task being animated.
     * @param stage The stage holding the task being animated. If null, it is the divider.
     * @param roundCorners Whether we should round the corners of the task while animating.
     * @param addShrinkAnimation Whether we should a shrink-and-grow effect to the task while it is
     * @param isGoingBehind Whether we should a shrink-and-grow effect to the task while it is
     *                           moving. (Simulates moving behind the divider.)
     */
    private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
    private ValueAnimator moveSurface(SurfaceControl.Transaction t, StageTaskListener stage,
            Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners,
            boolean addShrinkAnimation) {
            boolean isGoingBehind, boolean addVeil) {
        final boolean isApp = stage != null; // check if this is an app or a divider
        final SurfaceControl leash = isApp ? stage.getRootLeash() : getDividerLeash();
        final ActivityManager.RunningTaskInfo taskInfo = isApp ? stage.getRunningTaskInfo() : null;
        final SplitDecorManager decorManager = isApp ? stage.getDecorManager() : null;

        Rect tempStart = new Rect(start);
        Rect tempEnd = new Rect(end);
        final float diffX = tempEnd.left - tempStart.left;
@@ -810,7 +827,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
            int width = (int) (tempStart.width() + progress * diffWidth);
            int height = (int) (tempStart.height() + progress * diffHeight);

            if (addShrinkAnimation) {
            if (isGoingBehind) {
                float shrinkDiffX; // the position adjustments needed for this frame
                float shrinkDiffY;
                float shrinkScaleX; // the scale adjustments needed for this frame
@@ -863,9 +880,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
                t.setScale(leash, shrinkScaleX, shrinkScaleY);
            }

            // Set layers
            if (taskInfo != null) {
                t.setLayer(leash, isGoingBehind ? BEHIND_APP_LAYER : FRONT_APP_LAYER);
            } else {
                t.setLayer(leash, DIVIDER_LAYER);
            }

            if (offsetX == 0 && offsetY == 0) {
                t.setPosition(leash, instantaneousX, instantaneousY);
                mTempRect.set((int) instantaneousX, (int) instantaneousY,
                        (int) (instantaneousX + width), (int) (instantaneousY + height));
                t.setWindowCrop(leash, width, height);
                if (addVeil) {
                    decorManager.drawNextVeilFrameForSwapAnimation(
                            taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
                }
            } else {
                final int diffOffsetX = (int) (progress * offsetX);
                final int diffOffsetY = (int) (progress * offsetY);
@@ -873,6 +903,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
                mTempRect.set(0, 0, width, height);
                mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
                t.setCrop(leash, mTempRect);
                if (addVeil) {
                    decorManager.drawNextVeilFrameForSwapAnimation(
                            taskInfo, mTempRect, t, isGoingBehind, leash, diffOffsetX, diffOffsetY);
                }
            }
            t.apply();
        });
+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import com.android.wm.shell.shared.TransitionUtil;
public class SplitScreenConstants {
    /** Duration used for every split fade-in or fade-out. */
    public static final int FADE_DURATION = 133;
    /** Duration where we keep an app veiled to allow it to redraw itself behind the scenes. */
    public static final int VEIL_DELAY_DURATION = 400;

    /** Key for passing in widget intents when invoking split from launcher workspace. */
    public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
+28 −27
Original line number Diff line number Diff line
@@ -129,10 +129,10 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -1450,40 +1450,41 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mTempRect1.setEmpty();
        final StageTaskListener topLeftStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
        final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
                topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
        final StageTaskListener bottomRightStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
        final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
                bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
        mSplitLayout.playSwapAnimation(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,

        // Don't allow windows or divider to be focused during animation (mRootTaskInfo is the
        // parent of all 3 leaves). We don't want the user to be able to tap and focus a window
        // while it is moving across the screen, because granting focus also recalculates the
        // layering order, which is in delicate balance during this animation.
        WindowContainerTransaction noFocus = new WindowContainerTransaction();
        noFocus.setFocusable(mRootTaskInfo.token, false);
        mSyncQueue.queue(noFocus);

        mSplitLayout.playSwapAnimation(t, topLeftStage, bottomRightStage,
                insets -> {
                    // Runs at the end of the swap animation
                    SplitDecorManager decorManager1 = topLeftStage.getDecorManager();
                    SplitDecorManager decorManager2 = bottomRightStage.getDecorManager();

                    WindowContainerTransaction wct = new WindowContainerTransaction();

                    // Restore focus-ability to the windows and divider
                    wct.setFocusable(mRootTaskInfo.token, true);

                    setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
                    mSyncQueue.queue(wct);
                    mSyncQueue.runInSync(st -> {
                        updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
                        st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
                        st.setPosition(bottomRightScreenshot, insets.left, insets.top);

                        final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
                        va.addUpdateListener(valueAnimator-> {
                            final float progress = (float) valueAnimator.getAnimatedValue();
                            t.setAlpha(topLeftScreenshot, progress);
                            t.setAlpha(bottomRightScreenshot, progress);
                            t.apply();
                        });
                        va.addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(
                                    @androidx.annotation.NonNull Animator animation) {
                                t.remove(topLeftScreenshot);
                                t.remove(bottomRightScreenshot);
                                t.apply();
                                mTransactionPool.release(t);
                            }
                        });
                        va.start();

                        // updateSurfaceBounds(), above, officially puts the two apps in their new
                        // stages. Starting on the next frame, all calculations are made using the
                        // new layouts/insets. So any follow-up animations on the same leashes below
                        // should contain some cleanup/repositioning to prevent jank.

                        // Play follow-up animations if needed
                        decorManager1.fadeOutVeilAndCleanUp(st);
                        decorManager2.fadeOutVeilAndCleanUp(st);
                    });
                });

+14 −2

File changed.

Preview size limit exceeded, changes collapsed.