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

Commit 11139e91 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Schedule PIP mode changes at the beginning/end of the transitions." into oc-dev

parents c025ea89 8bca9e47
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -10535,8 +10535,9 @@ public class ActivityManagerService extends IActivityManager.Stub
                        final PinnedActivityStack pinnedStack =
                                mStackSupervisor.getStack(PINNED_STACK_ID);
                        if (pinnedStack != null) {
                            pinnedStack.animateResizePinnedStack(null /* sourceBounds */,
                                    destBounds, animationDuration);
                            pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
                                    destBounds, animationDuration,
                                    false /* schedulePipModeChangedOnAnimationEnd */);
                        }
                    } else {
                        throw new IllegalArgumentException("Stack: " + stackId
+7 −0
Original line number Diff line number Diff line
@@ -588,6 +588,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
                true /* includingParents */);
    }

    /**
     * Returns whether to defer the scheduling of the multi-window mode.
     */
    boolean deferScheduleMultiWindowModeChanged() {
        return false;
    }

    /**
     * Defers updating the bounds of the stack. If the stack was resized/repositioned while
     * deferring, the bounds will update in {@link #continueUpdateBounds()}.
+10 −7
Original line number Diff line number Diff line
@@ -2504,7 +2504,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        // incorrect if AMS.resizeStackWithBoundsFromWindowManager() is already called while waiting
        // for the AMS lock to be freed. So check and make sure these bounds are still good.
        final PinnedStackWindowController stackController = stack.getWindowContainerController();
        if (stackController.pinnedStackResizeAllowed()) {
        if (stackController.pinnedStackResizeDisallowed()) {
            return;
        }

@@ -2873,7 +2873,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        return true;
    }

    void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, float aspectRatio,
    void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
            boolean moveHomeStackToFront, String reason) {

        mWindowManager.deferSurfaceLayout();
@@ -2948,11 +2948,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
                aspectRatio, false /* useExistingStackBounds */);

        // TODO(b/36099777): Schedule the PiP mode change here immediately until we can defer all
        // callbacks until after the bounds animation
        scheduleUpdatePictureInPictureModeIfNeeded(r.getTask(), destBounds, true /* immediate */);

        stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */);
        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
                true /* schedulePipModeChangedOnAnimationEnd */);
        mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
    }

@@ -4179,6 +4176,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
    }

    void scheduleUpdateMultiWindowMode(TaskRecord task) {
        // If the stack is animating in a way where we will be forcing a multi-mode change at the
        // end, then ensure that we defer all in between multi-window mode changes
        if (task.getStack().deferScheduleMultiWindowModeChanged()) {
            return;
        }

        for (int i = task.mActivities.size() - 1; i >= 0; i--) {
            final ActivityRecord r = task.mActivities.get(i);
            if (r.app != null && r.app.thread != null) {
+16 −4
Original line number Diff line number Diff line
@@ -43,9 +43,10 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
    }

    void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, int animationDuration) {
        getWindowContainerController().animateResizePinnedStack(sourceBounds, destBounds,
                animationDuration);
    void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
            boolean schedulePipModeChangedOnAnimationEnd) {
        getWindowContainerController().animateResizePinnedStack(toBounds, sourceHintBounds,
                animationDuration, schedulePipModeChangedOnAnimationEnd);
    }

    void setPictureInPictureAspectRatio(float aspectRatio) {
@@ -60,7 +61,18 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
        return getWindowContainerController().isAnimatingBoundsToFullscreen();
    }

    @Override
    /**
     * Returns whether to defer the scheduling of the multi-window mode.
     */
    boolean deferScheduleMultiWindowModeChanged() {
        // For the pinned stack, the deferring of the multi-window mode changed is tied to the
        // transition animation into picture-in-picture, and is called once the animation completes,
        // or is interrupted in a way that would leave the stack in a non-fullscreen state.
        // @see BoundsAnimationController
        // @see BoundsAnimationControllerTests
        return mWindowContainerController.deferScheduleMultiWindowModeChanged();
    }

    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) {
        // It is guaranteed that the activities requiring the update will be in the pinned stack at
        // this point (either reparented before the animation into PiP, or before reparenting after
+115 −116
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
@@ -35,6 +36,9 @@ import android.view.WindowManagerInternal;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Enables animating bounds of objects.
 *
@@ -43,7 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
 * relaunching it would cause poorer experience), these class provides a way to directly animate
 * the bounds of the resized object.
 *
 * The object that is resized needs to implement {@link AnimateBoundsUser} interface.
 * The object that is resized needs to implement {@link BoundsAnimationTarget} interface.
 *
 * NOTE: All calls to methods in this class should be done on the UI thread
 */
@@ -56,8 +60,19 @@ public class BoundsAnimationController {

    private static final int DEFAULT_TRANSITION_DURATION = 425;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START,
        SCHEDULE_PIP_MODE_CHANGED_ON_END})
    public @interface SchedulePipModeChangedState {}
    /** Do not schedule any PiP mode changed callbacks as a part of this animation. */
    public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;
    /** Schedule a PiP mode changed callback when this animation starts. */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;
    /** Schedule a PiP mode changed callback when this animation ends. */
    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;

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

    private final class AppTransitionNotifier
            extends WindowManagerInternal.AppTransitionListener implements Runnable {
@@ -108,40 +123,42 @@ public class BoundsAnimationController {
    @VisibleForTesting
    final class BoundsAnimator extends ValueAnimator
            implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
        private final AnimateBoundsUser mTarget;
        private final BoundsAnimationTarget mTarget;
        private final Rect mFrom = new Rect();
        private final Rect mTo = new Rect();
        private final Rect mTmpRect = new Rect();
        private final Rect mTmpTaskBounds = new Rect();
        private final boolean mMoveToFullScreen;
        // True if this this animation was cancelled and will be replaced the another animation from
        // the same {@link #AnimateBoundsUser} target.

        // True if this this animation was canceled and will be replaced the another animation from
        // the same {@link #BoundsAnimationTarget} target.
        private boolean mSkipFinalResize;
        // True if this animation replaced a previous animation of the same
        // {@link #AnimateBoundsUser} target.
        // {@link #BoundsAnimationTarget} target.
        private final boolean mSkipAnimationStart;
        // True if this animation was cancelled by the user, not as a part of a replacing animation
        // True if this animation was canceled by the user, not as a part of a replacing animation
        private boolean mSkipAnimationEnd;
        // True if this animation is not replacing a previous animation, or if the previous
        // animation is animating to a different fullscreen state than the current animation.
        // We use this to ensure that we always provide a consistent set/order of callbacks when we
        // transition to/from PiP.
        private final boolean mAnimatingToNewFullscreenState;
        // True if the animation target should be moved to the fullscreen stack at the end of this
        // animation
        private boolean mMoveToFullscreen;

        // Whether to schedule PiP mode changes on animation start/end
        private @SchedulePipModeChangedState int mSchedulePipModeChangedState;

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

        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen,
                boolean replacingExistingAnimation, boolean animatingToNewFullscreenState) {
        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
                @SchedulePipModeChangedState int schedulePipModeChangedState,
                boolean moveToFullscreen, boolean replacingExistingAnimation) {
            super();
            mTarget = target;
            mFrom.set(from);
            mTo.set(to);
            mMoveToFullScreen = moveToFullScreen;
            mSkipAnimationStart = replacingExistingAnimation;
            mAnimatingToNewFullscreenState = animatingToNewFullscreenState;
            mSchedulePipModeChangedState = schedulePipModeChangedState;
            mMoveToFullscreen = moveToFullscreen;
            addUpdateListener(this);
            addListener(this);

@@ -161,7 +178,8 @@ public class BoundsAnimationController {
        @Override
        public void onAnimationStart(Animator animation) {
            if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
                    + " mSkipAnimationStart=" + mSkipAnimationStart);
                    + " mSkipAnimationStart=" + mSkipAnimationStart
                    + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
            mFinishAnimationAfterTransition = false;
            mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
                    mFrom.top + mFrozenTaskHeight);
@@ -170,13 +188,8 @@ public class BoundsAnimationController {
            // we trigger any size changes, so it can swap surfaces
            // in to appropriate modes, or do as it wishes otherwise.
            if (!mSkipAnimationStart) {
                mTarget.onAnimationStart(mMoveToFullScreen);
            }

            // If we are animating to a new fullscreen state (either to/from fullscreen), then
            // notify the target of the change with the new frozen task bounds
            if (mAnimatingToNewFullscreenState && mMoveToFullScreen) {
                mTarget.updatePictureInPictureMode(null);
                mTarget.onAnimationStart(mSchedulePipModeChangedState ==
                        SCHEDULE_PIP_MODE_CHANGED_ON_START);
            }

            // Immediately update the task bounds if they have to become larger, but preserve
@@ -206,20 +219,26 @@ public class BoundsAnimationController {
                // any further animation.
                if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");

                // If we have already scheduled a PiP mode changed at the start of the animation,
                // then we need to clean up and schedule one at the end, since we have canceled the
                // animation to the final state.
                if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }

                // Since we are cancelling immediately without a replacement animation, send the
                // animation end to maintain callback parity, but also skip any further resizes
                prepareCancel(false /* skipAnimationEnd */, true /* skipFinalResize */);
                cancel();
                cancelAndCallAnimationEnd();
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
                    + " mMoveToFullScreen=" + mMoveToFullScreen
                    + " mSkipFinalResize=" + mSkipFinalResize
                    + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning());
                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning()
                    + " callers=" + Debug.getCallers(2));

            // There could be another animation running. For example in the
            // move to fullscreen case, recents will also be closing while the
@@ -231,58 +250,57 @@ public class BoundsAnimationController {
                return;
            }

            if (!mSkipFinalResize) {
                // If not cancelled, resize the pinned stack to the final size. All calls to
                // setPinnedStackSize() must be done between onAnimationStart() and onAnimationEnd()
                mTarget.setPinnedStackSize(mTo, null);
            if (!mSkipAnimationEnd) {
                // If this animation has already scheduled the picture-in-picture mode on start, and
                // we are not skipping the final resize due to being canceled, then move the PiP to
                // fullscreen once the animation ends
                if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
                        + " moveToFullscreen=" + mMoveToFullscreen);
                mTarget.onAnimationEnd(mSchedulePipModeChangedState ==
                        SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null,
                                mMoveToFullscreen);
            }

            finishAnimation();

            if (mMoveToFullScreen && !mSkipFinalResize) {
                mTarget.moveToFullscreen();
            }
            // Clean up this animation
            removeListener(this);
            removeUpdateListener(this);
            mRunningAnimations.remove(mTarget);
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            finishAnimation();
            // Always skip the final resize when the animation is canceled
            mSkipFinalResize = true;
            mMoveToFullscreen = false;
        }

        public void prepareCancel(boolean skipAnimationEnd, boolean skipFinalResize) {
            if (DEBUG) Slog.d(TAG, "prepareCancel: skipAnimationEnd=" + skipAnimationEnd
                    + " skipFinalResize=" + skipFinalResize);
            mSkipAnimationEnd = skipAnimationEnd;
            mSkipFinalResize = skipFinalResize;
        private void cancelAndCallAnimationEnd() {
            if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget);
            mSkipAnimationEnd = false;
            super.cancel();
        }

        @Override
        public void cancel() {
            if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget);
            mSkipAnimationEnd = true;
            super.cancel();
        }

        /** Returns true if the animation target is the same as the input bounds. */
        /**
         * @return true if the animation target is the same as the input bounds.
         */
        boolean isAnimatingTo(Rect bounds) {
            return mTo.equals(bounds);
        }

        private boolean animatingToLargerSize() {
            if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) {
                return false;
            }
            return true;
        }

        private void finishAnimation() {
            if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget
                    + " callers" + Debug.getCallers(2));
            if (!mSkipAnimationEnd) {
                mTarget.onAnimationEnd();
            }
            removeListener(this);
            removeUpdateListener(this);
            mRunningAnimations.remove(mTarget);
        /**
         * @return true if we are animating to a larger surface size
         */
        @VisibleForTesting
        boolean animatingToLargerSize() {
            // TODO: Fix this check for aspect ratio changes
            return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height());
        }

        @Override
@@ -291,63 +309,23 @@ public class BoundsAnimationController {
        }
    }

    public interface AnimateBoundsUser {
        /**
         * Sets the size of the target (without any intermediate steps, like scheduling animation)
         * but freezes the bounds of any tasks in the target at taskBounds,
         * to allow for more flexibility during resizing. Only works for the pinned stack at the
         * moment.
         *
         * @return Whether the target should continue to be animated and this call was successful.
         * If false, the animation will be cancelled because the user has determined that the
         * animation is now invalid and not required. In such a case, the cancel will trigger the
         * animation end callback as well, but will not send any further size changes.
         */
        boolean setPinnedStackSize(Rect bounds, Rect taskBounds);

        /**
         * Callback for the target to inform it that the animation has started, so it can do some
         * necessary preparation.
         */
        void onAnimationStart(boolean toFullscreen);

        /**
         * Callback for the target to inform it that the animation is going to a new fullscreen
         * state and should update the picture-in-picture mode accordingly.
         *
         * @param targetStackBounds the target stack bounds we are animating to, can be null if
         *                          we are animating to fullscreen
         */
        void updatePictureInPictureMode(Rect targetStackBounds);

        /**
         * Callback for the target to inform it that the animation has ended, so it can do some
         * necessary cleanup.
         */
        void onAnimationEnd();

        /**
         * Callback for the target to inform it to reparent to the fullscreen stack.
         */
        void moveToFullscreen();
    }

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

    @VisibleForTesting
    BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to,
            int animationDuration, boolean moveToFullscreen) {
    BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
            boolean moveToFullscreen) {
        final BoundsAnimator existing = mRunningAnimations.get(target);
        final boolean replacing = existing != null;
        final boolean animatingToNewFullscreenState = (existing == null) ||
                (existing.mMoveToFullScreen != moveToFullscreen);

        if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
                + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing
                + " animatingToNewFullscreenState=" + animatingToNewFullscreenState);
                + " schedulePipModeChangedState=" + schedulePipModeChangedState
                + " replacing=" + replacing);

        if (replacing) {
            if (existing.isAnimatingTo(to)) {
@@ -355,15 +333,36 @@ public class BoundsAnimationController {
                // one we are trying to start.
                if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
                        + " ignoring...");

                return existing;
            }
            // Since we are replacing, we skip both animation start and end callbacks, and don't
            // animate to the final bounds when cancelling
            existing.prepareCancel(true /* skipAnimationEnd */, true /* skipFinalResize */);

            // Update the PiP callback states if we are replacing the animation
            if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep"
                            + " existing deferred state");
                } else {
                    if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback"
                            + " on start already processed, schedule deferred update on end");
                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }
            } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) {
                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
                    if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled,"
                            + " callback on start will be processed");
                } else {
                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep"
                            + " existing deferred state");
                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
                }
            }

            // Since we are replacing, we skip both animation start and end callbacks
            existing.cancel();
        }
        final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen,
                replacing, animatingToNewFullscreenState);
        final BoundsAnimator animator = new BoundsAnimator(target, from, to,
                schedulePipModeChangedState, moveToFullscreen, replacing);
        mRunningAnimations.put(target, animator);
        animator.setFloatValues(0f, 1f);
        animator.setDuration((animationDuration != -1 ? animationDuration
Loading