Loading core/java/com/android/internal/policy/PipSnapAlgorithm.java +93 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); } Loading @@ -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); } /** Loading core/res/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -1570,6 +1570,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" /> Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +5 −4 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +7 −3 Original line number Diff line number Diff line Loading @@ -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 }; Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading Loading
core/java/com/android/internal/policy/PipSnapAlgorithm.java +93 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); } Loading @@ -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); } /** Loading
core/res/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -1570,6 +1570,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" /> Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +5 −4 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +7 −3 Original line number Diff line number Diff line Loading @@ -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 }; Loading Loading @@ -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) { Loading @@ -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; Loading Loading @@ -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); Loading