From 57d2255cc983b614be61cab86ea8d5cafc03fdec Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Thu, 9 Mar 2017 15:37:13 -0800 Subject: [PATCH] Add drag down to dismiss pip as a dismiss option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If enabled via flag, when the user drags the PIP past a threshold at the bottom of the screen, the PIP will be dismissed. If the PIP is sitting near the bottom edge and the user flings downwards the PIP will be dismissed. Unifies logic to identify for “fling towards edge” for minimize and dismiss gesture — adds additional logic to check that the start of the fling gesture was near that edge. This stops you from accidentally minimizing or dismissing on a “long fling”. Test: manual - enable flag, and drag pip to bottom of screen to dismiss Bug: 35358628 Change-Id: Iddd66ed2720964d431cddea6173767eb5565b991 --- .../systemui/pip/phone/PipMotionHelper.java | 44 ++++++++- .../systemui/pip/phone/PipTouchHandler.java | 89 ++++++++++++------- .../systemui/pip/phone/PipTouchState.java | 7 ++ 3 files changed, 105 insertions(+), 35 deletions(-) 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 010522d2818d3..ae622a4269c4f 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(); } + 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.