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 20c1136a59988..49d89a2bdd68a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -56,7 +56,8 @@ public class PipMotionHelper { private static final int DEFAULT_MOVE_STACK_DURATION = 225; private static final int SNAP_STACK_DURATION = 225; - private static final int DISMISS_STACK_DURATION = 375; + private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375; + private static final int DRAG_TO_DISMISS_STACK_DURATION = 175; private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; private static final int EXPAND_STACK_TO_MENU_DURATION = 250; private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300; @@ -65,6 +66,8 @@ public class PipMotionHelper { // The fraction of the stack width that the user has to drag offscreen to minimize the PiP private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f; + // The fraction of the stack height that the user has to drag offscreen to minimize the PiP + private static final float DISMISS_OFFSCREEN_FRACTION = 0.35f; private Context mContext; private IActivityManager mActivityManager; @@ -193,6 +196,19 @@ public class PipMotionHelper { } } + /** + * @return whether the PiP at the current bounds should be dismissed. + */ + boolean shouldDismissPip() { + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + if (mBounds.bottom > displaySize.y) { + float offscreenFraction = (float) (mBounds.bottom - displaySize.y) / mBounds.height(); + return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION; + } + return false; + } + /** * Flings the minimized PiP to the closest minimized snap target. */ @@ -297,16 +313,38 @@ public class PipMotionHelper { resizeAndAnimatePipUnchecked(toBounds, IME_SHIFT_DURATION); } + /** + * Animates the dismissal of the PiP off the edge of the screen. + */ + Rect animateDragToEdgeDismiss(Rect pipBounds) { + cancelAnimations(); + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + Rect toBounds = new Rect(pipBounds); + toBounds.offset(0, displaySize.y - pipBounds.top); + mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION, + FAST_OUT_LINEAR_IN, mUpdateBoundsListener); + mBoundsAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dismissPip(); + } + }); + mBoundsAnimator.start(); + return toBounds; + } + /** * Animates the dismissal of the PiP over the dismiss target bounds. */ - Rect animateDismissFromDrag(Rect dismissBounds) { + Rect animateDragToTargetDismiss(Rect dismissBounds) { cancelAnimations(); Rect toBounds = new Rect(dismissBounds.centerX(), dismissBounds.centerY(), dismissBounds.centerX() + 1, dismissBounds.centerY() + 1); - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DISMISS_STACK_DURATION, + mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, + DRAG_TO_TARGET_DISMISS_STACK_DURATION, FAST_OUT_LINEAR_IN, mUpdateBoundsListener); mBoundsAnimator.addListener(new AnimatorListenerAdapter() { @Override 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 8e6a32837c297..7eaecdf7991ab 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -52,7 +52,8 @@ public class PipTouchHandler { private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200; // Allow dragging the PIP to a location to close it - private static final boolean ENABLE_DRAG_TO_DISMISS = false; + private static final boolean ENABLE_DISMISS_DRAG_TO_TARGET = false; + private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = false; private final Context mContext; private final IActivityManager mActivityManager; @@ -78,7 +79,7 @@ public class PipTouchHandler { private Runnable mShowDismissAffordance = new Runnable() { @Override public void run() { - if (ENABLE_DRAG_TO_DISMISS) { + if (ENABLE_DISMISS_DRAG_TO_TARGET) { mDismissViewController.showDismissTarget(mMotionHelper.getBounds()); } } @@ -378,7 +379,7 @@ public class PipTouchHandler { mMenuController.pokeMenu(); } - if (ENABLE_DRAG_TO_DISMISS) { + if (ENABLE_DISMISS_DRAG_TO_TARGET) { mDismissViewController.createDismissTarget(); mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); } @@ -394,7 +395,7 @@ public class PipTouchHandler { mSavedSnapFraction = -1f; } - if (touchState.startedDragging() && ENABLE_DRAG_TO_DISMISS) { + if (touchState.startedDragging() && ENABLE_DISMISS_DRAG_TO_TARGET) { mHandler.removeCallbacks(mShowDismissAffordance); mDismissViewController.showDismissTarget(mMotionHelper.getBounds()); } @@ -408,11 +409,16 @@ public class PipTouchHandler { if (!touchState.allowDraggingOffscreen()) { left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left)); } - top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top)); + if (ENABLE_DISMISS_DRAG_TO_EDGE) { + // Allow pip to move past bottom bounds + top = Math.max(mMovementBounds.top, top); + } else { + top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top)); + } mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds); - if (ENABLE_DRAG_TO_DISMISS) { + if (ENABLE_DISMISS_DRAG_TO_TARGET) { mDismissViewController.updateDismissTarget(mTmpBounds); } return true; @@ -427,7 +433,7 @@ public class PipTouchHandler { } try { - if (ENABLE_DRAG_TO_DISMISS) { + if (ENABLE_DISMISS_DRAG_TO_TARGET) { mHandler.removeCallbacks(mShowDismissAffordance); PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); @@ -435,7 +441,7 @@ public class PipTouchHandler { && velocity < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { if (mDismissViewController.shouldDismiss(mMotionHelper.getBounds())) { Rect dismissBounds = mDismissViewController.getDismissBounds(); - mMotionHelper.animateDismissFromDrag(dismissBounds); + mMotionHelper.animateDragToTargetDismiss(dismissBounds); MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, METRIC_VALUE_DISMISSED_BY_DRAG); @@ -448,9 +454,17 @@ public class PipTouchHandler { } if (touchState.isDragging()) { - PointF vel = mTouchState.getVelocity(); - if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() - || isHorizontalFlingTowardsCurrentEdge(vel))) { + final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX(); + boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */); + if (ENABLE_DISMISS_DRAG_TO_EDGE + && (mMotionHelper.shouldDismissPip() || isFlingToBot)) { + mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds()); + MetricsLogger.action(mContext, + MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, + METRIC_VALUE_DISMISSED_BY_DRAG); + return true; + } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() + || isFlingTowardsEdge(touchState, onLeft ? 2 : 3))) { // Pip should be minimized setMinimizedStateInternal(true); if (mMenuController.isMenuVisible()) { @@ -475,6 +489,7 @@ public class PipTouchHandler { mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); } + final PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds); @@ -495,29 +510,39 @@ public class PipTouchHandler { }; /** - * @return whether the gesture ending in the {@param vel} is fast enough to be a fling towards - * the same edge the PIP is on. Used to identify a minimize gesture. + * @return whether the gesture ending in {@param vel} is fast enough to be a fling and towards + * the provided {@param edge} where: + * + * 1 = top + * 2 = left + * 3 = right + * 4 = bottom */ - private boolean isHorizontalFlingTowardsCurrentEdge(PointF vel) { - final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y); - final boolean isFling = PointF.length(vel.x, vel.y) > mFlingAnimationUtils - .getMinVelocityPxPerSecond(); - final boolean towardsCurrentEdge = isOverEdge(true /* left */) && vel.x < 0 - || isOverEdge(false /* right */) && vel.x > 0; - return towardsCurrentEdge && isHorizontal && isFling; - } - - /** - * @return whether the given bounds are on the left or right edge (depending on - * {@param checkLeft}) - */ - private boolean isOverEdge(boolean checkLeft) { + private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) { + final PointF vel = touchState.getVelocity(); + final PointF downPos = touchState.getDownTouchPosition(); final Rect bounds = mMotionHelper.getBounds(); - if (checkLeft) { - return bounds.left <= mMovementBounds.left; - } else { - return bounds.right >= mMovementBounds.right + bounds.width(); + final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y); + final boolean isFling = + PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond(); + if (!isFling) { + return false; } + switch (edge) { + case 1: // top + return !isHorizontal && vel.y < 0 + && downPos.y <= mMovementBounds.top + bounds.height(); + case 2: // left + return isHorizontal && vel.x < 0 + && downPos.x <= mMovementBounds.left + bounds.width(); + case 3: // right + return isHorizontal && vel.x > 0 + && downPos.x >= mMovementBounds.right; + case 4: // bottom + return !isHorizontal && vel.y > 0 + && downPos.y >= mMovementBounds.bottom; + } + return false; } /** @@ -542,7 +567,7 @@ public class PipTouchHandler { pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction); - pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DRAG_TO_DISMISS); + pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DISMISS_DRAG_TO_TARGET); mSnapAlgorithm.dump(pw, innerPrefix); mTouchState.dump(pw, innerPrefix); mMotionHelper.dump(pw, innerPrefix); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java index a317dc32ba455..b34a07d329438 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java @@ -169,6 +169,13 @@ public class PipTouchState { return mLastDelta; } + /** + * @return the down touch position. + */ + public PointF getDownTouchPosition() { + return mDownTouch; + } + /** * @return the movement delta between the last handled touch event and the down touch * position.