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

Commit 15b29c7b authored by Mady Mellor's avatar Mady Mellor
Browse files

Adjustments to PIP position while flinging

Rather than using a scroller, find the actual edge intercept
based on the trajectory of the fling.

This is done by finding the two points it could intersect with
and checking which point is 'closer' (i.e. would be hit first
by the PIP).

Bias towards using the intersection with the top / bottom edge
if the PIP is being flung along the side it's currently on.

Also increases the maximum time for the fling.

Bug: 35358634
Test: manual - fling PIP around screen while in landscape
      and portrait
Change-Id: I26e943a5ddbc726ab86bc11e4271d4db034f3d47
parent 8b5b0c2b
Loading
Loading
Loading
Loading
+93 −17
Original line number Diff line number Diff line
@@ -49,9 +49,6 @@ public class PipSnapAlgorithm {
    // Allows snapping on the long edge in each orientation and magnets towards corners
    private static final int SNAP_MODE_LONG_EDGE_MAGNET_CORNERS = 4;

    // The friction multiplier to control how slippery the PIP is when flung
    private static final float SCROLL_FRICTION_MULTIPLIER = 8f;

    // Threshold to magnet to a corner
    private static final float CORNER_MAGNET_THRESHOLD = 0.3f;

@@ -64,8 +61,8 @@ public class PipSnapAlgorithm {
    private final float mDefaultSizePercent;
    private final float mMinAspectRatioForMinSize;
    private final float mMaxAspectRatioForMinSize;
    private final int mFlingDeceleration;

    private Scroller mScroller;
    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;

    private final int mMinimizedVisibleSize;
@@ -81,6 +78,8 @@ public class PipSnapAlgorithm {
        mMaxAspectRatioForMinSize = res.getFloat(
                com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
        mFlingDeceleration = mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.pip_fling_deceleration);
        onConfigurationChanged();
    }

@@ -107,20 +106,97 @@ public class PipSnapAlgorithm {
     * those for the given {@param stackBounds}.
     */
    public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds, float velocityX,
            float velocityY) {
        final Rect finalStackBounds = new Rect(stackBounds);
        if (mScroller == null) {
            final ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
            mScroller = new Scroller(mContext);
            mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
        }
        mScroller.fling(stackBounds.left, stackBounds.top,
                (int) velocityX, (int) velocityY,
                movementBounds.left, movementBounds.right,
                movementBounds.top, movementBounds.bottom);
        finalStackBounds.offsetTo(mScroller.getFinalX(), mScroller.getFinalY());
        mScroller.abortAnimation();
        return findClosestSnapBounds(movementBounds, finalStackBounds);
            float velocityY, Point dragStartPosition) {
        final Rect intersectStackBounds = new Rect(stackBounds);
        final Point intersect = getEdgeIntersect(stackBounds, movementBounds, velocityX, velocityY,
                dragStartPosition);
        intersectStackBounds.offsetTo(intersect.x, intersect.y);
        return findClosestSnapBounds(movementBounds, intersectStackBounds);
    }

    /**
     * @return The point along the {@param movementBounds} that the PIP would intersect with based
     *         on the provided {@param velX}, {@param velY} along with the position of the PIP when
     *         the gesture started, {@param dragStartPosition}.
     */
    public Point getEdgeIntersect(Rect stackBounds, Rect movementBounds, float velX, float velY,
            Point dragStartPosition) {
        final boolean isLandscape = mOrientation == Configuration.ORIENTATION_LANDSCAPE;
        final int x = stackBounds.left;
        final int y = stackBounds.top;

        // Find the line of movement the PIP is on. Line defined by: y = slope * x + yIntercept
        final float slope = velY / velX; // slope = rise / run
        final float yIntercept = y - slope * x; // rearrange line equation for yIntercept
        // The PIP can have two intercept points:
        // 1) Where the line intersects with one of the edges of the screen (vertical line)
        Point vertPoint = new Point();
        // 2) Where the line intersects with the top or bottom of the screen (horizontal line)
        Point horizPoint = new Point();

        // Find the vertical line intersection, x will be one of the edges
        vertPoint.x = velX > 0 ? movementBounds.right : movementBounds.left;
        // Sub in x in our line equation to determine y position
        vertPoint.y = findY(slope, yIntercept, vertPoint.x);

        // Find the horizontal line intersection, y will be the top or bottom of the screen
        horizPoint.y = velY > 0 ? movementBounds.bottom : movementBounds.top;
        // Sub in y in our line equation to determine x position
        horizPoint.x = findX(slope, yIntercept, horizPoint.y);

        // Now pick one of these points -- first determine if we're flinging along the current edge.
        // Only fling along current edge if it's a direction with space for the PIP to move to
        int maxDistance;
        if (isLandscape) {
            maxDistance = velX > 0
                    ? movementBounds.right - stackBounds.left
                    : stackBounds.left - movementBounds.left;
        } else {
            maxDistance = velY > 0
                    ? movementBounds.bottom - stackBounds.top
                    : stackBounds.top - movementBounds.top;
        }
        if (maxDistance > 0) {
            // Only fling along the current edge if the start and end point are on the same side
            final int startPoint = isLandscape ? dragStartPosition.y : dragStartPosition.x;
            final int endPoint = isLandscape ? horizPoint.y : horizPoint.x;
            final int center = movementBounds.centerX();
            if ((startPoint < center && endPoint < center)
                    || (startPoint > center && endPoint > center)) {
                // We are flinging along the current edge, figure out how far it should travel
                // based on velocity and assumed deceleration.
                int distance = (int) (0 - Math.pow(isLandscape ? velX : velY, 2))
                        / (2 * mFlingDeceleration);
                distance = Math.min(distance, maxDistance);
                // Adjust the point for the distance
                if (isLandscape) {
                    horizPoint.x = stackBounds.left + (velX > 0 ? distance : -distance);
                } else {
                    horizPoint.y = stackBounds.top + (velY > 0 ? distance : -distance);
                }
                return horizPoint;
            }
        }
        // If we're not flinging along the current edge, find the closest point instead.
        final double distanceVert = Math.hypot(vertPoint.x - x, vertPoint.y - y);
        final double distanceHoriz = Math.hypot(horizPoint.x - x, horizPoint.y - y);
        // Ensure that we're actually going somewhere
        if (distanceVert == 0) {
            return horizPoint;
        }
        if (distanceHoriz == 0) {
            return vertPoint;
        }
        // Otherwise use the closest point
        return Math.abs(distanceVert) > Math.abs(distanceHoriz) ? horizPoint : vertPoint;
    }

    private int findY(float slope, float yIntercept, float x) {
        return (int) ((slope * x) + yIntercept);
    }

    private int findX(float slope, float yIntercept, float y) {
        return (int) ((y - yIntercept) / slope);
    }

    /**
+3 −0
Original line number Diff line number Diff line
@@ -67,6 +67,9 @@
    <!-- The amount to leave on-screen when the PIP is minimized. -->
    <dimen name="pip_minimized_visible_size">48dp</dimen>

    <!-- The the PIP decelerates at while moving from a fling. -->
    <dimen name="pip_fling_deceleration">-3000dp</dimen>

    <!-- Min width for a tablet device -->
    <dimen name="min_xlarge_screen_width">800dp</dimen>

+1 −0
Original line number Diff line number Diff line
@@ -1583,6 +1583,7 @@
  <java-symbol type="dimen" name="docked_stack_divider_insets" />
  <java-symbol type="dimen" name="docked_stack_minimize_thickness" />
  <java-symbol type="dimen" name="pip_minimized_visible_size" />
  <java-symbol type="dimen" name="pip_fling_deceleration" />
  <java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
  <java-symbol type="integer" name="config_pictureInPictureSnapMode" />
  <java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
+5 −4
Original line number Diff line number Diff line
@@ -241,14 +241,14 @@ public class PipMotionHelper implements Handler.Callback {
    /**
     * Flings the minimized PiP to the closest minimized snap target.
     */
    Rect flingToMinimizedState(float velocityY, Rect movementBounds) {
    Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
        cancelAnimations();
        // We currently only allow flinging the minimized stack up and down, so just lock the
        // movement bounds to the current stack bounds horizontally
        movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
                movementBounds.bottom);
        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
                0 /* velocityX */, velocityY);
                0 /* velocityX */, velocityY, dragStartPosition);
        if (!mBounds.equals(toBounds)) {
            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
@@ -281,10 +281,11 @@ public class PipMotionHelper implements Handler.Callback {
     * Flings the PiP to the closest snap target.
     */
    Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
            AnimatorUpdateListener updateListener, AnimatorListener listener) {
            AnimatorUpdateListener updateListener, AnimatorListener listener,
            Point startPosition) {
        cancelAnimations();
        Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
                velocityX, velocityY);
                velocityX, velocityY, startPosition);
        if (!mBounds.equals(toBounds)) {
            mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
            mFlingAnimationUtils.apply(mBoundsAnimator, 0,
+7 −3
Original line number Diff line number Diff line
@@ -184,7 +184,7 @@ public class PipTouchHandler {
        mDismissViewController = new PipDismissViewController(context);
        mSnapAlgorithm = new PipSnapAlgorithm(mContext);
        mTouchState = new PipTouchState(mViewConfig);
        mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
        mFlingAnimationUtils = new FlingAnimationUtils(context, 2.5f);
        mGestures = new PipTouchGesture[] {
                mDefaultMovementGesture
        };
@@ -532,6 +532,7 @@ public class PipTouchHandler {
    private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
        // Whether the PiP was on the left side of the screen at the start of the gesture
        private boolean mStartedOnLeft;
        private Point mStartPosition;

        @Override
        public void onDown(PipTouchState touchState) {
@@ -539,7 +540,9 @@ public class PipTouchHandler {
                return;
            }

            mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
            Rect bounds = mMotionHelper.getBounds();
            mStartPosition = new Point(bounds.left, bounds.top);
            mStartedOnLeft = bounds.left < mMovementBounds.centerX();
            mMovementWithinMinimize = true;
            mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;

@@ -685,7 +688,8 @@ public class PipTouchHandler {

                if (isFling) {
                    mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
                            mUpdateScrimListener, postAnimationCallback);
                            mUpdateScrimListener, postAnimationCallback,
                            mStartPosition);
                } else {
                    mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
                            postAnimationCallback);