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

Commit 14fefc26 authored by Winson Chung's avatar Winson Chung
Browse files

Fixing issue with PIP while IME is showing.



- Unifying logic to ensure that the PIP is moved consistently as its
  movement bounds are shifted.  This is done by adding a snap fraction
  which is a fraction relative to one set of movement bounds, and applied
  to a new movement bounds.  This is flexible to work with all of the
  current snap modes being tested.
- Fixing issue where you can drag out of bounds when touching the PIP
  before the IME shows.

Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testPinnedStackOffsetForIME
Change-Id: Ie68c1ca599f6196726b8224585974a0972b93701
Signed-off-by: default avatarWinson Chung <winsonc@google.com>
parent 97cc85fd
Loading
Loading
Loading
Loading
+92 −14
Original line number Diff line number Diff line
@@ -53,7 +53,14 @@ public class PipSnapAlgorithm {

    public PipSnapAlgorithm(Context context) {
        mContext = context;
        mOrientation = context.getResources().getConfiguration().orientation;
        onConfigurationChanged();
    }

    /**
     * Updates the snap algorithm when the configuration changes.
     */
    public void onConfigurationChanged() {
        mOrientation = mContext.getResources().getConfiguration().orientation;
        calculateSnapTargets();
    }

@@ -90,19 +97,7 @@ public class PipSnapAlgorithm {
        final Rect newBounds = new Rect(stackBounds);
        if (mSnapMode == SNAP_MODE_EDGE) {
            // Find the closest edge to the given stack bounds and snap to it
            final int fromLeft = stackBounds.left - movementBounds.left;
            final int fromTop = stackBounds.top - movementBounds.top;
            final int fromRight = movementBounds.right - stackBounds.left;
            final int fromBottom = movementBounds.bottom - stackBounds.top;
            if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) {
                newBounds.offset(-fromLeft, 0);
            } else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) {
                newBounds.offset(0, -fromTop);
            } else if (fromRight < fromLeft && fromRight < fromTop && fromRight < fromBottom) {
                newBounds.offset(fromRight, 0);
            } else {
                newBounds.offset(0, fromBottom);
            }
            snapRectToClosestEdge(stackBounds, movementBounds, newBounds);
        } else {
            // Find the closest snap point
            final Rect tmpBounds = new Rect();
@@ -118,6 +113,68 @@ public class PipSnapAlgorithm {
        return newBounds;
    }

    /**
     * @return returns a fraction that describes where along the {@param movementBounds} the
     *         {@param stackBounds} are. If the {@param stackBounds} are not currently on the
     *         {@param movementBounds} exactly, then they will be snapped to the movement bounds.
     *
     *         The fraction is defined in a clockwise fashion against the {@param movementBounds}:
     *
     *            0   1
     *          4 +---+ 1
     *            |   |
     *          3 +---+ 2
     *            3   2
     */
    public float getSnapFraction(Rect stackBounds, Rect movementBounds) {
        final Rect tmpBounds = new Rect();
        snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds);
        final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
                movementBounds.width();
        final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
                movementBounds.height();
        if (tmpBounds.top == movementBounds.top) {
            return widthFraction;
        } else if (tmpBounds.left == movementBounds.right) {
            return 1f + heightFraction;
        } else if (tmpBounds.top == movementBounds.bottom) {
            return 2f + (1f - widthFraction);
        } else {
            return 3f + (1f - heightFraction);
        }
    }

    /**
     * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
     * See {@link #getSnapFraction(Rect, Rect)}.
     *
     * The fraction is define in a clockwise fashion against the {@param movementBounds}:
     *
     *    0   1
     *  4 +---+ 1
     *    |   |
     *  3 +---+ 2
     *    3   2
     */
    public void applySnapFraction(Rect stackBounds, Rect movementBounds, float snapFraction) {
        if (snapFraction < 1f) {
            int offset = movementBounds.left + (int) (snapFraction * movementBounds.width());
            stackBounds.offsetTo(offset, movementBounds.top);
        } else if (snapFraction < 2f) {
            snapFraction -= 1f;
            int offset = movementBounds.top + (int) (snapFraction * movementBounds.height());
            stackBounds.offsetTo(movementBounds.right, offset);
        } else if (snapFraction < 3f) {
            snapFraction -= 2f;
            int offset = movementBounds.left + (int) ((1f - snapFraction) * movementBounds.width());
            stackBounds.offsetTo(offset, movementBounds.bottom);
        } else {
            snapFraction -= 3f;
            int offset = movementBounds.top + (int) ((1f - snapFraction) * movementBounds.height());
            stackBounds.offsetTo(movementBounds.left, offset);
        }
    }

    /**
     * @return the closest point in {@param points} to the given {@param x} and {@param y}.
     */
@@ -134,6 +191,27 @@ public class PipSnapAlgorithm {
        return closestPoint;
    }

    /**
     * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
     * the new bounds out to {@param boundsOut}.
     */
    private void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut) {
        final int fromLeft = Math.abs(stackBounds.left - movementBounds.left);
        final int fromTop = Math.abs(stackBounds.top - movementBounds.top);
        final int fromRight = Math.abs(movementBounds.right - stackBounds.left);
        final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top);
        boundsOut.set(stackBounds);
        if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) {
            boundsOut.offsetTo(movementBounds.left, stackBounds.top);
        } else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) {
            boundsOut.offsetTo(stackBounds.left, movementBounds.top);
        } else if (fromRight < fromLeft && fromRight < fromTop && fromRight < fromBottom) {
            boundsOut.offsetTo(movementBounds.right, stackBounds.top);
        } else {
            boundsOut.offsetTo(stackBounds.left, movementBounds.bottom);
        }
    }

    /**
     * @return the distance between point {@param p} and the given {@param x} and {@param y}.
     */
+13 −6
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ public class PipTouchHandler implements TunerService.Tunable {

    private final PipInputEventReceiver mInputEventReceiver;
    private PipDismissViewController mDismissViewController;
    private PipSnapAlgorithm mSnapAlgorithm;
    private final PipSnapAlgorithm mSnapAlgorithm;
    private PipMotionHelper mMotionHelper;

    private boolean mEnableSwipeToDismiss = true;
@@ -163,6 +163,7 @@ public class PipTouchHandler implements TunerService.Tunable {
        if (mEnableDragToDismiss) {
            mDismissViewController = new PipDismissViewController(context);
        }
        mSnapAlgorithm = new PipSnapAlgorithm(mContext);
        mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
        mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());

@@ -187,7 +188,8 @@ public class PipTouchHandler implements TunerService.Tunable {
    }

    public void onConfigurationChanged() {
        updateBoundedPinnedStackBounds();
        mSnapAlgorithm.onConfigurationChanged();
        updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
    }

    private void handleTouchEvent(MotionEvent ev) {
@@ -203,7 +205,7 @@ public class PipTouchHandler implements TunerService.Tunable {
                    mPinnedStackBoundsAnimator.cancel();
                }

                updateBoundedPinnedStackBounds();
                updateBoundedPinnedStackBounds(true /* updatePinnedStackBounds */);
                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                mActivePointerId = ev.getPointerId(0);
@@ -299,6 +301,10 @@ public class PipTouchHandler implements TunerService.Tunable {
                float velocityY = mVelocityTracker.getYVelocity();
                float velocity = PointF.length(velocityX, velocityY);

                // Update the movement bounds again if the state has changed since the user started
                // dragging (ie. when the IME shows)
                updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);

                if (mIsSwipingToDismiss) {
                    if (Math.abs(velocityX) > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
                        flingToDismiss(velocityX);
@@ -462,14 +468,15 @@ public class PipTouchHandler implements TunerService.Tunable {
    /**
     * Updates the movement bounds of the pinned stack.
     */
    private void updateBoundedPinnedStackBounds() {
    private void updateBoundedPinnedStackBounds(boolean updatePinnedStackBounds) {
        try {
            StackInfo info = mActivityManager.getStackInfo(PINNED_STACK_ID);
            if (info != null) {
                if (updatePinnedStackBounds) {
                    mPinnedStackBounds.set(info.bounds);
                }
                mBoundedPinnedStackBounds.set(mWindowManager.getPictureInPictureMovementBounds(
                        info.displayId));
                mSnapAlgorithm = new PipSnapAlgorithm(mContext);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Could not fetch PIP movement bounds.", e);
+53 −70
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ import android.util.Log;
import android.util.Size;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -68,9 +68,11 @@ class PinnedStackController {
    private boolean mInInteractiveMode;
    private boolean mIsImeShowing;
    private int mImeHeight;
    private final Rect mPreImeShowingBounds = new Rect();
    private ValueAnimator mBoundsAnimator = null;

    // Used to calculate stack bounds across rotations
    private final DisplayInfo mDisplayInfo = new DisplayInfo();

    // The size and position information that describes where the pinned stack will go by default.
    private int mDefaultStackGravity;
    private Size mDefaultStackSize;
@@ -93,7 +95,6 @@ class PinnedStackController {
                    mBoundsAnimator.cancel();
                }
                mInInteractiveMode = inInteractiveMode;
                mPreImeShowingBounds.setEmpty();
            });
        }
    }
@@ -116,6 +117,7 @@ class PinnedStackController {
        mDisplayContent = displayContent;
        mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
        mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
        mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
        reloadResources();
    }

@@ -159,12 +161,8 @@ class PinnedStackController {
     * @return the default bounds to show the PIP when there is no active PIP.
     */
    Rect getDefaultBounds() {
        final Display display = mDisplayContent.getDisplay();
        final Rect insetBounds = new Rect();
        final Point displaySize = new Point();
        display.getRealSize(displaySize);
        mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mTmpInsets);
        getInsetBounds(displaySize, mTmpInsets, insetBounds);
        getInsetBounds(insetBounds);

        final Rect defaultBounds = new Rect();
        Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(),
@@ -177,12 +175,16 @@ class PinnedStackController {
     *         controller.
     */
    Rect getMovementBounds(Rect stackBounds) {
        final Display display = mDisplayContent.getDisplay();
        return getMovementBounds(stackBounds, true /* adjustForIme */);
    }

    /**
     * @return the movement bounds for the given {@param stackBounds} and the current state of the
     *         controller.
     */
    Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
        final Rect movementBounds = new Rect();
        final Point displaySize = new Point();
        display.getRealSize(displaySize);
        mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mTmpInsets);
        getInsetBounds(displaySize, mTmpInsets, movementBounds);
        getInsetBounds(movementBounds);

        // Adjust the right/bottom to ensure the stack bounds never goes offscreen
        movementBounds.right = Math.max(movementBounds.left, movementBounds.right -
@@ -190,30 +192,34 @@ class PinnedStackController {
        movementBounds.bottom = Math.max(movementBounds.top, movementBounds.bottom -
                stackBounds.height());

        // Adjust the top if the ime is open
        // Apply the movement bounds adjustments based on the current state
        if (adjustForIme) {
            if (mIsImeShowing) {
                movementBounds.bottom -= mImeHeight;
            }

        }
        return movementBounds;
    }

    /**
     * @return the PIP bounds given it's bounds pre-rotation, and post-rotation (with as applied
     * by the display content, which currently transposes the dimensions but keeps each stack in
     * the same physical space on the device).
     * @return the repositioned PIP bounds given it's pre-change bounds, and the new display info.
     */
    Rect getPostRotationBounds(Rect preRotationStackBounds, Rect postRotationStackBounds) {
        // Keep the pinned stack in the same aspect ratio as in the old orientation, but
        // move it into the position in the rotated space, and snap to the closest space
        // in the new orientation.
        final Rect movementBounds = getMovementBounds(preRotationStackBounds);
        final int stackWidth = preRotationStackBounds.width();
        final int stackHeight = preRotationStackBounds.height();
        final int left = postRotationStackBounds.centerX() - (stackWidth / 2);
        final int top = postRotationStackBounds.centerY() - (stackHeight / 2);
        final Rect postRotBounds = new Rect(left, top, left + stackWidth, top + stackHeight);
        return mSnapAlgorithm.findClosestSnapBounds(movementBounds, postRotBounds);
    Rect onDisplayChanged(Rect preChangeStackBounds, DisplayInfo displayInfo) {
        final Rect postChangeStackBounds = new Rect(preChangeStackBounds);
        if (!mDisplayInfo.equals(displayInfo)) {
            // Calculate the snap fraction of the current stack along the old movement bounds, and
            // then update the stack bounds to the same fraction along the rotated movement bounds.
            final Rect preChangeMovementBounds = getMovementBounds(preChangeStackBounds);
            final float snapFraction = mSnapAlgorithm.getSnapFraction(preChangeStackBounds,
                    preChangeMovementBounds);
            mDisplayInfo.copyFrom(displayInfo);

            final Rect postChangeMovementBounds = getMovementBounds(preChangeStackBounds,
                    false /* adjustForIme */);
            mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
                    snapFraction);
        }
        return postChangeStackBounds;
    }

    /**
@@ -228,7 +234,6 @@ class PinnedStackController {
        final Rect stackBounds = new Rect();
        mService.getStackBounds(PINNED_STACK_ID, stackBounds);
        final Rect prevMovementBounds = getMovementBounds(stackBounds);
        final boolean wasAdjustedForIme = mIsImeShowing;
        mIsImeShowing = adjustedForIme;
        mImeHeight = imeHeight;
        if (mInInteractiveMode) {
@@ -238,32 +243,18 @@ class PinnedStackController {
        } else {
            // Otherwise, we can move the PIP to a sane location to ensure that it does not block
            // the user from interacting with the IME
            Rect toBounds;
            if (!wasAdjustedForIme && adjustedForIme) {
                // If we are showing the IME, then store the previous bounds
                mPreImeShowingBounds.set(stackBounds);
                toBounds = adjustBoundsInMovementBounds(stackBounds);
            } else if (wasAdjustedForIme && !adjustedForIme) {
                if (!mPreImeShowingBounds.isEmpty()) {
                    // If we are hiding the IME and the user is not interacting with the PIP, restore
                    // the previous bounds
                    toBounds = mPreImeShowingBounds;
            final Rect movementBounds = getMovementBounds(stackBounds);
            final Rect toBounds = new Rect(stackBounds);
            if (adjustedForIme) {
                // IME visible
                toBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
            } else {
                // IME hidden
                if (stackBounds.top == prevMovementBounds.bottom) {
                        // If the PIP is resting on top of the IME, then adjust it with the hiding
                        // of the IME
                        final Rect movementBounds = getMovementBounds(stackBounds);
                        toBounds = new Rect(stackBounds);
                    // If the PIP is resting on top of the IME, then adjust it with the hiding IME
                    toBounds.offsetTo(toBounds.left, movementBounds.bottom);
                    } else {
                        // Otherwise, leave the PIP in place
                        toBounds = stackBounds;
                }
            }
            } else {
                // Otherwise, the IME bounds have changed so we need to adjust the PIP bounds also
                toBounds = adjustBoundsInMovementBounds(stackBounds);
            }
            if (!toBounds.equals(stackBounds)) {
                if (mBoundsAnimator != null) {
                    mBoundsAnimator.cancel();
@@ -274,16 +265,6 @@ class PinnedStackController {
        }
    }

    /**
     * @return the adjusted {@param stackBounds} such that they are in the movement bounds.
     */
    private Rect adjustBoundsInMovementBounds(Rect stackBounds) {
        final Rect movementBounds = getMovementBounds(stackBounds);
        final Rect adjustedBounds = new Rect(stackBounds);
        adjustedBounds.offset(0, Math.min(0, movementBounds.bottom - stackBounds.top));
        return adjustedBounds;
    }

    /**
     * Sends a broadcast that the PIP movement bounds have changed.
     */
@@ -300,10 +281,12 @@ class PinnedStackController {
    /**
     * @return the bounds on the screen that the PIP can be visible in.
     */
    private void getInsetBounds(Point displaySize, Rect insets, Rect outRect) {
        outRect.set(insets.left + mScreenEdgeInsets.x, insets.top + mScreenEdgeInsets.y,
                displaySize.x - insets.right - mScreenEdgeInsets.x,
                displaySize.y - insets.bottom - mScreenEdgeInsets.y);
    private void getInsetBounds(Rect outRect) {
        mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
                mDisplayInfo.logicalHeight, mTmpInsets);
        outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
                mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
                mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
    }

    /**
+9 −2
Original line number Diff line number Diff line
@@ -387,8 +387,8 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
        mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
        switch (mStackId) {
            case PINNED_STACK_ID:
                mTmpRect2 = mDisplayContent.getPinnedStackController().getPostRotationBounds(
                        mBounds, mTmpRect2);
                mTmpRect2 = mDisplayContent.getPinnedStackController().onDisplayChanged(mBounds,
                        getDisplayInfo());
                break;
            case DOCKED_STACK_ID:
                repositionDockedStackAfterRotation(mTmpRect2);
@@ -627,6 +627,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
                "animation background stackId=" + mStackId);

        final Rect oldBounds = new Rect(mBounds);
        Rect bounds = null;
        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
        if (mStackId == DOCKED_STACK_ID
@@ -651,6 +652,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye

        updateDisplayInfo(bounds);

        // Update the pinned stack controller after the display info is updated
        if (mStackId == PINNED_STACK_ID) {
            mDisplayContent.getPinnedStackController().onDisplayChanged(oldBounds,
                    getDisplayInfo());
        }

        super.onDisplayChanged(dc);
    }