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

Commit 57d2255c authored by Mady Mellor's avatar Mady Mellor
Browse files

Add drag down to dismiss pip as a dismiss option

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
parent 9c2886ba
Loading
Loading
Loading
Loading
+41 −3
Original line number Diff line number Diff line
@@ -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
+57 −32
Original line number Diff line number Diff line
@@ -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));
                }
                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) {
    private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) {
        final PointF vel = touchState.getVelocity();
        final PointF downPos = touchState.getDownTouchPosition();
        final Rect bounds = mMotionHelper.getBounds();
        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;
        final boolean isFling =
                PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond();
        if (!isFling) {
            return false;
        }

    /**
     * @return whether the given bounds are on the left or right edge (depending on
     *         {@param checkLeft})
     */
    private boolean isOverEdge(boolean checkLeft) {
        final Rect bounds = mMotionHelper.getBounds();
        if (checkLeft) {
            return bounds.left <= mMovementBounds.left;
        } else {
            return bounds.right >= mMovementBounds.right + bounds.width();
        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);
+7 −0
Original line number Diff line number Diff line
@@ -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.