diff options
5 files changed, 109 insertions, 24 deletions
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index 95d714f1c3c7..749d00c136ec 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -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); + 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; } - 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); + 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); } /** diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 5e2334d20da1..fa33d567983e 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f39c24985b17..885f0a3a9ecc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1581,6 +1581,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" /> diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index b8771d7e0fb6..cebb22f07aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 3682ae655f7c..9588b03b53bd 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -185,7 +185,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 }; @@ -534,6 +534,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) { @@ -541,7 +542,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; @@ -687,7 +690,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); |