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

Commit 9c675d4f authored by Tracy Zhou's avatar Tracy Zhou
Browse files

Implement the new PiP animation (fade-in).

The new fully gestural navigation changes how user goes home from an
app as well as the animation of the app transition. The current PiP animation (bounds) is not compatible with the new model because of the direction of movements. Instead of animating bounds, we can fade the PiP window in after app closing animation finishes.

Fixes: 122609330
Test: 1. Open youtube, play a video, tap home to observe the new PiP
animation (demo video attached in the ticket b/122609330) 2. With gestural nav on, observe pip transition when swiping up to home.
atest ActivityManagerPinnedStackTests
atest BoundsAnimationControllerTests
atest RecentsAnimationTestTest

Change-Id: I28eeb1aa99c4fd569845ca7a42561f6b20796f9b
parent 1be371dd
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -41,9 +41,11 @@ interface IRecentsAnimationController {
     * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then
     * the home activity should be moved to the top. Otherwise, the home activity is hidden and the
     * user is returned to the app.
     * @param sendUserLeaveHint If set to true, {@link Activity#onUserLeaving} will be sent to the
     *                          top resumed app, false otherwise.
     */
    @UnsupportedAppUsage
    void finish(boolean moveHomeToTop);
    void finish(boolean moveHomeToTop, boolean sendUserLeaveHint);

    /**
     * Called by the handler to indicate that the recents animation input consumer should be
+8 −2
Original line number Diff line number Diff line
@@ -77,9 +77,15 @@ public class RecentsAnimationControllerCompat {
        }
    }

    public void finish(boolean toHome) {
    /**
     * Finish the current recents animation.
     * @param toHome Going to home or back to the previous app.
     * @param sendUserLeaveHint determines whether userLeaveHint will be set true to the previous
     *                          app.
     */
    public void finish(boolean toHome, boolean sendUserLeaveHint) {
        try {
            mAnimationController.finish(toHome);
            mAnimationController.finish(toHome, sendUserLeaveHint);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to finish recents animation", e);
        }
+74 −19
Original line number Diff line number Diff line
@@ -73,6 +73,13 @@ public class BoundsAnimationController {
    /** Schedule a PiP mode changed callback when this animation ends. */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;

    public static final int BOUNDS = 0;
    public static final int FADE_IN = 1;

    @IntDef({BOUNDS, FADE_IN}) public @interface AnimationType {}

    private static final int FADE_IN_DURATION = 500;

    // Only accessed on UI thread.
    private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();

@@ -115,6 +122,7 @@ public class BoundsAnimationController {
    private boolean mFinishAnimationAfterTransition = false;
    private final AnimationHandler mAnimationHandler;
    private Choreographer mChoreographer;
    private @AnimationType int mAnimationType;

    private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000;

@@ -140,6 +148,7 @@ public class BoundsAnimationController {
            implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {

        private final BoundsAnimationTarget mTarget;
        private final @AnimationType int mAnimationType;
        private final Rect mFrom = new Rect();
        private final Rect mTo = new Rect();
        private final Rect mTmpRect = new Rect();
@@ -166,8 +175,8 @@ public class BoundsAnimationController {

        // Depending on whether we are animating from
        // a smaller to a larger size
        private final int mFrozenTaskWidth;
        private final int mFrozenTaskHeight;
        private int mFrozenTaskWidth;
        private int mFrozenTaskHeight;

        // Timeout callback to ensure we continue the animation if waiting for resuming or app
        // windows drawn fails
@@ -176,12 +185,13 @@ public class BoundsAnimationController {
            resume();
        };

        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
                @SchedulePipModeChangedState int schedulePipModeChangedState,
        BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from,
                Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState,
                @SchedulePipModeChangedState int prevShedulePipModeChangedState,
                boolean moveFromFullscreen, boolean moveToFullscreen, Rect frozenTask) {
            super();
            mTarget = target;
            mAnimationType = animationType;
            mFrom.set(from);
            mTo.set(to);
            mSchedulePipModeChangedState = schedulePipModeChangedState;
@@ -195,6 +205,7 @@ public class BoundsAnimationController {
            // to their final size immediately so we can use scaling to make the window
            // larger. Likewise if we are going from bigger to smaller, we want to wait until
            // the end so we don't have to upscale from the smaller finished size.
            if (mAnimationType == BOUNDS) {
                if (animatingToLargerSize()) {
                    mFrozenTaskWidth = mTo.width();
                    mFrozenTaskHeight = mTo.height();
@@ -203,6 +214,7 @@ public class BoundsAnimationController {
                    mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height();
                }
            }
        }

        @Override
        public void onAnimationStart(Animator animation) {
@@ -222,8 +234,9 @@ public class BoundsAnimationController {
            // otherwise.
            boolean continueAnimation;
            if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
                continueAnimation = mTarget.onAnimationStart(mSchedulePipModeChangedState ==
                        SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
                continueAnimation = mTarget.onAnimationStart(
                        mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START,
                        false /* forceUpdate */, mAnimationType);

                // When starting an animation from fullscreen, pause here and wait for the
                // windows-drawn signal before we start the rest of the transition down into PiP.
@@ -238,7 +251,8 @@ public class BoundsAnimationController {
                // However, we still need to report to them that they are leaving PiP, so this will
                // force an update via a mode changed callback.
                continueAnimation = mTarget.onAnimationStart(
                        true /* schedulePipModeChangedCallback */, true /* forceUpdate */);
                        true /* schedulePipModeChangedCallback */, true /* forceUpdate */,
                        mAnimationType);
            } else {
                // The animation is already running, but we should check that the TaskStack is still
                // valid before continuing with the animation
@@ -285,6 +299,13 @@ public class BoundsAnimationController {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            final float value = (Float) animation.getAnimatedValue();
            if (mAnimationType == FADE_IN) {
                if (!mTarget.setPinnedStackAlpha(value)) {
                    cancelAndCallAnimationEnd();
                }
                return;
            }

            final float remains = 1 - value;
            mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
            mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
@@ -408,16 +429,29 @@ public class BoundsAnimationController {

    public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
            boolean moveFromFullscreen, boolean moveToFullscreen) {
            boolean moveFromFullscreen, boolean moveToFullscreen,
            @AnimationType int animationType) {
        animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
                moveFromFullscreen, moveToFullscreen);
                moveFromFullscreen, moveToFullscreen, animationType);
    }

    @VisibleForTesting
    BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
            boolean moveFromFullscreen, boolean moveToFullscreen) {
            boolean moveFromFullscreen, boolean moveToFullscreen,
            @AnimationType int animationType) {
        final BoundsAnimator existing = mRunningAnimations.get(target);
        // animateBoundsImpl gets called twice for each animation. The second time we get the final
        // to rect that respects the shelf, which is when we want to resize. Our signal for fade in
        // comes in from how to enter into pip, but we also need to use the to and from rect to
        // decide which animation we want to run finally.
        boolean shouldResize = false;
        if (isRunningFadeInAnimation(target)) {
            shouldResize = true;
            if (from.contains(to)) {
                animationType = FADE_IN;
            }
        }
        final boolean replacing = existing != null;
        @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
                NO_PIP_MODE_CHANGED_CALLBACKS;
@@ -477,18 +511,34 @@ public class BoundsAnimationController {
            // Since we are replacing, we skip both animation start and end callbacks
            existing.cancel();
        }
        final BoundsAnimator animator = new BoundsAnimator(target, from, to,
        if (shouldResize) {
            target.setPinnedStackSize(to, null);
        }
        final BoundsAnimator animator = new BoundsAnimator(target, animationType, from, to,
                schedulePipModeChangedState, prevSchedulePipModeChangedState,
                moveFromFullscreen, moveToFullscreen, frozenTask);
        mRunningAnimations.put(target, animator);
        animator.setFloatValues(0f, 1f);
        animator.setDuration((animationDuration != -1 ? animationDuration
                : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
        animator.setDuration(animationType == FADE_IN ? FADE_IN_DURATION
                : (animationDuration != -1 ? animationDuration : DEFAULT_TRANSITION_DURATION)
                        * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.start();
        return animator;
    }

    public void setAnimationType(@AnimationType int animationType) {
        mAnimationType = animationType;
    }

    /** return the current animation type. */
    public @AnimationType int getAnimationType() {
        @AnimationType int animationType = mAnimationType;
        // Default to BOUNDS.
        mAnimationType = BOUNDS;
        return animationType;
    }

    public Handler getHandler() {
        return mHandler;
    }
@@ -498,6 +548,11 @@ public class BoundsAnimationController {
        mHandler.post(this::resume);
    }

    private boolean isRunningFadeInAnimation(final BoundsAnimationTarget target) {
        final BoundsAnimator existing = mRunningAnimations.get(target);
        return existing != null && existing.mAnimationType == FADE_IN && existing.isStarted();
    }

    private void resume() {
        for (int i = 0; i < mRunningAnimations.size(); i++) {
            final BoundsAnimator b = mRunningAnimations.valueAt(i);
+5 −1
Original line number Diff line number Diff line
@@ -32,7 +32,8 @@ interface BoundsAnimationTarget {
     * callbacks
     * @return whether to continue the animation
     */
    boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
    boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate,
            @BoundsAnimationController.AnimationType int animationType);

    /**
     * @return Whether the animation should be paused waiting for the windows to draw before
@@ -53,6 +54,9 @@ interface BoundsAnimationTarget {
     */
    boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds);

    /** Sets the alpha of the animation target */
    boolean setPinnedStackAlpha(float alpha);

    /**
     * Callback for the target to inform it that the animation has ended, so it can do some
     * necessary cleanup.
+23 −5
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.WindowManager.TRANSIT_NONE;

import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.BoundsAnimationController.BOUNDS;
import static com.android.server.wm.BoundsAnimationController.FADE_IN;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -201,7 +203,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
        }
    }

    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
            boolean sendUserLeaveHint) {
        synchronized (mService.mGlobalLock) {
            if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
                    + mWindowManager.getRecentsAnimationController()
@@ -246,7 +249,18 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
                    if (reorderMode == REORDER_MOVE_TO_TOP) {
                        // Bring the target stack to the front
                        mStackSupervisor.mNoAnimActivities.add(targetActivity);

                        if (sendUserLeaveHint) {
                            // Setting this allows the previous app to PiP.
                            mStackSupervisor.mUserLeaving = true;
                            targetStack.moveTaskToFrontLocked(targetActivity.getTaskRecord(),
                                    true /* noAnimation */, null /* activityOptions */,
                                    targetActivity.appTimeTracker,
                                    "RecentsAnimation.onAnimationFinished()");
                        } else {
                            targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
                        }

                        if (DEBUG) {
                            final ActivityStack topStack = getTopNonAlwaysOnTopStack();
                            if (topStack != targetStack) {
@@ -300,11 +314,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks,

    @Override
    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
            boolean runSychronously) {
            boolean runSychronously, boolean sendUserLeaveHint) {
        if (runSychronously) {
            finishAnimation(reorderMode);
            finishAnimation(reorderMode, sendUserLeaveHint);
        } else {
            mService.mH.post(() -> finishAnimation(reorderMode));
            mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint));
        }
    }

@@ -317,6 +331,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
        }
        final RecentsAnimationController controller =
                mWindowManager.getRecentsAnimationController();
        final DisplayContent dc =
                mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent;
        dc.mBoundsAnimationController.setAnimationType(
                controller.shouldCancelWithDeferredScreenshot() ? FADE_IN : BOUNDS);

        // Cancel running recents animation and screenshot previous task when the next
        // transition starts in below cases:
Loading