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

Commit 19953caa authored by Winson Chung's avatar Winson Chung
Browse files

Tightening up rotation behavior for PIP (2/3)

- Change BoundsAnimationController to be more consistent:
  1) Ensure that on animation end is always called even when cancelled to
     ensure animation start/end parity in the callbacks
  2) Ensure that setPinnedStackSize() is only called between start/end
  3) Prevent calling setPinnedStackSize() to the final bounds if the
     animation is cancelled
- With that, we add a flag to cancel the current bounds animation when a
  rotation happens while the bounds are animating.  In addition, we also
  add a check from AM to WM before applying the resize during the animation
  in the case where WM sends the bounds to AM, but AM lock is held while
  updating the exact stack bounds (once that finishes the old stale bounds
  would have been applied)
- In addition, we can then move the handling of the of the rotation to the
  config change update in WM, if we handle it before the other checks.

Bug: 36879891
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: I62c6df8b349971cc82a7898ae8b26834723faec5
parent 3a68287c
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -174,6 +174,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.WindowManagerService;

import java.io.FileDescriptor;
@@ -2492,11 +2493,21 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
    }

    void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
        final ActivityStack stack = getStack(PINNED_STACK_ID);
        final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
        if (stack == null) {
            Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
            return;
        }

        // It is possible for the bounds animation from the WM to call this but be delayed by
        // another AM call that is holding the AMS lock. In such a case, the pinnedBounds may be
        // 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()) {
            return;
        }

        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
        mWindowManager.deferSurfaceLayout();
        try {
+52 −23
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.WindowManagerInternal;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Enables animating bounds of objects.
 *
@@ -103,7 +105,8 @@ public class BoundsAnimationController {
                com.android.internal.R.interpolator.fast_out_slow_in);
    }

    private final class BoundsAnimator extends ValueAnimator
    @VisibleForTesting
    final class BoundsAnimator extends ValueAnimator
            implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
        private final AnimateBoundsUser mTarget;
        private final Rect mFrom = new Rect();
@@ -113,10 +116,12 @@ public class BoundsAnimationController {
        private final boolean mMoveToFullScreen;
        // True if this this animation was cancelled and will be replaced the another animation from
        // the same {@link #AnimateBoundsUser} target.
        private boolean mSkipAnimationEnd;
        private boolean mSkipFinalResize;
        // True if this animation replaced a previous animation of the same
        // {@link #AnimateBoundsUser} target.
        private final boolean mSkipAnimationStart;
        // True if this animation was cancelled 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
@@ -200,7 +205,11 @@ public class BoundsAnimationController {
                // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
                // any further animation.
                if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
                animation.cancel();

                // 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();
            }
        }

@@ -208,7 +217,7 @@ public class BoundsAnimationController {
        public void onAnimationEnd(Animator animation) {
            if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
                    + " mMoveToFullScreen=" + mMoveToFullScreen
                    + " mSkipAnimationEnd=" + mSkipAnimationEnd
                    + " mSkipFinalResize=" + mSkipFinalResize
                    + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning());

@@ -222,10 +231,15 @@ 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);
            }

            finishAnimation();

            mTarget.setPinnedStackSize(mTo, null);
            if (mMoveToFullScreen && !mSkipAnimationEnd) {
            if (mMoveToFullScreen && !mSkipFinalResize) {
                mTarget.moveToFullscreen();
            }
        }
@@ -235,10 +249,16 @@ public class BoundsAnimationController {
            finishAnimation();
        }

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

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

@@ -273,19 +293,15 @@ public class BoundsAnimationController {

    public interface AnimateBoundsUser {
        /**
         * Asks the target to directly (without any intermediate steps, like scheduling animation)
         * resize its bounds.
         *
         * @return Whether the target still wants to be animated and successfully finished the
         * operation. If it returns false, the animation will immediately be cancelled. The target
         * should return false when something abnormal happened, e.g. it was completely removed
         * from the hierarchy and is not valid anymore.
         */
        boolean setSize(Rect bounds);
        /**
         * Behaves as setSize, but freezes the bounds of any tasks in the target at taskBounds,
         * 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);

@@ -310,11 +326,20 @@ public class BoundsAnimationController {
         */
        void onAnimationEnd();

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

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

    @VisibleForTesting
    BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to,
            int animationDuration, boolean moveToFullscreen) {
        final BoundsAnimator existing = mRunningAnimations.get(target);
        final boolean replacing = existing != null;
        final boolean animatingToNewFullscreenState = (existing == null) ||
@@ -326,12 +351,15 @@ public class BoundsAnimationController {

        if (replacing) {
            if (existing.isAnimatingTo(to)) {
                // Just les the current animation complete if it has the same destination as the
                // Just let the current animation complete if it has the same destination as the
                // one we are trying to start.
                if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
                        + " ignoring...");
                return;
                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 */);
            existing.cancel();
        }
        final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen,
@@ -342,5 +370,6 @@ public class BoundsAnimationController {
                : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.start();
        return animator;
    }
}
+11 −2
Original line number Diff line number Diff line
@@ -244,10 +244,18 @@ class PinnedStackController {
     * Updates the display info, calculating and returning the new stack and movement bounds in the
     * new orientation of the device if necessary.
     */
    void onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
    boolean onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
        if (mDisplayInfo.equals(displayInfo)) {
            return;
            // We are already in the right orientation, ignore
            outBounds.setEmpty();
            return false;
        } else if (targetBounds.isEmpty()) {
            // The stack is null, we are just initializing the stack, so just store the display info
            // and ignore
            mDisplayInfo.copyFrom(displayInfo);
            outBounds.setEmpty();
            return false;
        }

        mTmpRect.set(targetBounds);
@@ -272,6 +280,7 @@ class PinnedStackController {
        notifyMovementBoundsChanged(false /* fromImeAdjustment */);

        outBounds.set(postChangeStackBounds);
        return true;
    }

    /**
+4 −0
Original line number Diff line number Diff line
@@ -122,6 +122,10 @@ public class PinnedStackWindowController extends StackWindowController {
        return mContainer.isBoundsAnimatingToFullscreen();
    }

    public boolean pinnedStackResizeAllowed() {
        return mContainer.pinnedStackResizeAllowed();
    }

    /**
     * Checks the {@param bounds} and retirms non-null fullscreen bounds for the pinned stack
     * animation if necessary.
+39 −33
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
    /** For comparison with DisplayContent bounds. */
    private Rect mTmpRect = new Rect();
    private Rect mTmpRect2 = new Rect();
    private Rect mTmpRect3 = new Rect();

    /** Content limits relative to the DisplayContent this sits in. */
    private Rect mBounds = new Rect();
@@ -126,6 +127,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
    // would otherwise apply while resizing, while resizing in the bounds animating mode.
    private boolean mBoundsAnimating = false;
    private boolean mBoundsAnimatingToFullscreen = false;
    private boolean mCancelCurrentBoundsAnimation = false;
    private Rect mBoundsAnimationTarget = new Rect();
    private Rect mBoundsAnimationSourceBounds = new Rect();

@@ -262,12 +264,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye

        if (mDisplayContent != null) {
            mDisplayContent.mDimLayerController.updateDimLayer(this);
            if (mStackId == PINNED_STACK_ID) {
                // Update the bounds based on any changes to the display info
                getAnimatingBounds(mTmpRect2);
                mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(mTmpRect2,
                        bounds);
            }
            mAnimationBackgroundSurface.setBounds(bounds);
        }

@@ -398,6 +394,24 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
            // as it's going away soon anyway.
            return false;
        }

        if (mStackId == PINNED_STACK_ID) {
            getAnimatingBounds(mTmpRect2);
            boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
                    mTmpRect2, mTmpRect3);
            if (updated) {
                mBoundsAfterRotation.set(mTmpRect3);

                // Once we've set the bounds based on the rotation of the old bounds in the new
                // orientation, clear the animation target bounds since they are obsolete, and
                // cancel any currently running animations
                mBoundsAnimationTarget.setEmpty();
                mBoundsAnimationSourceBounds.setEmpty();
                mCancelCurrentBoundsAnimation = true;
                return true;
            }
        }

        final int newRotation = getDisplayInfo().rotation;
        final int newDensity = getDisplayInfo().logicalDensityDpi;

@@ -413,20 +427,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
            return false;
        }

        if (StackId.tasksAreFloating(mStackId)) {
            // Update stack bounds again since the display info has changed since updateDisplayInfo,
            // however, for floating tasks, we don't need to apply the new rotation to the bounds,
            // we can just update and return them here
            setBounds(mBounds);
            mBoundsAfterRotation.set(mBounds);

            // Once we've set the bounds based on the rotation of the old bounds in the new
            // orientation, clear the animation target bounds since they are obsolete
            mBoundsAnimationTarget.setEmpty();
            mBoundsAnimationSourceBounds.setEmpty();
            return true;
        }

        mTmpRect2.set(mBounds);
        mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
        switch (mStackId) {
@@ -692,6 +692,14 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
            getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
                    mDisplayContent.mDividerControllerLocked.getContentWidth(),
                    dockedOnTopOrLeft);
        } else if (mStackId == PINNED_STACK_ID) {
            // Update the bounds based on any changes to the display info
            getAnimatingBounds(mTmpRect2);
            boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
                    mTmpRect2, mTmpRect3);
            if (updated) {
                bounds = new Rect(mTmpRect3);
            }
        }

        updateDisplayInfo(bounds);
@@ -1443,21 +1451,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
        }
    }

    @Override  // AnimatesBounds
    public boolean setSize(Rect bounds) {
        synchronized (mService.mWindowMap) {
            if (mDisplayContent == null) {
    public boolean setPinnedStackSize(Rect bounds, Rect tempTaskBounds) {
        if (mCancelCurrentBoundsAnimation) {
            return false;
        }
        }
        try {
            mService.mActivityManager.resizeStack(mStackId, bounds, false, true, false, -1);
        } catch (RemoteException e) {
        }
        return true;
    }

    public boolean setPinnedStackSize(Rect bounds, Rect tempTaskBounds) {
        try {
            mService.mActivityManager.resizePinnedStack(bounds, tempTaskBounds);
        } catch (RemoteException e) {
@@ -1471,6 +1469,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
        synchronized (mService.mWindowMap) {
            mBoundsAnimating = true;
            mBoundsAnimatingToFullscreen = toFullscreen;
            mCancelCurrentBoundsAnimation = false;
        }

        if (mStackId == PINNED_STACK_ID) {
@@ -1532,6 +1531,13 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
        return mBoundsAnimating && mBoundsAnimatingToFullscreen;
    }

    public boolean pinnedStackResizeAllowed() {
        if (mBoundsAnimating && mCancelCurrentBoundsAnimation) {
            return true;
        }
        return false;
    }

    /** Returns true if a removal action is still being deferred. */
    boolean checkCompleteDeferredRemoval() {
        if (isAnimating()) {
Loading