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

Commit 3a5e8dd8 authored by Mady Mellor's avatar Mady Mellor
Browse files

Revert "Revert "Improve interaction around showing / hiding the gear behind notifications""

This reverts commit 88eca2aa.

It additionally fixes an issue with notification scrolling after gear is displayed
by indicating that the user is no longer swiping by calling onDragCancelled

Change-Id: Ieba0cea35bba9c383d8b5fdca08cb5ff083666c3
parent bded63d9
Loading
Loading
Loading
Loading
+48 −27
Original line number Diff line number Diff line
@@ -517,35 +517,16 @@ public class SwipeHelper implements Gefingerpoken {
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mCurrView != null) {
                    float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
                    mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
                    float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
                    float velocity = getVelocity(mVelocityTracker);
                    float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);

                    float translation = getTranslation(mCurrView);
                    // Decide whether to dismiss the current view
                    boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
                            Math.abs(translation) > 0.4 * getSize(mCurrView);
                    boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
                            (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
                            (velocity > 0) == (translation > 0);
                    boolean falsingDetected = mCallback.isAntiFalsingNeeded();

                    if (mFalsingManager.isClassiferEnabled()) {
                        falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
                    } else {
                        falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
                if (mCurrView == null) {
                    break;
                }
                mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
                float velocity = getVelocity(mVelocityTracker);

                    boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
                            && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough)
                            && ev.getActionMasked() == MotionEvent.ACTION_UP;

                    if (dismissChild) {
                if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
                    if (isDismissGesture(ev)) {
                        // flingadingy
                        dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
                        dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f);
                    } else {
                        // snappity
                        mCallback.onDragCancelled(mCurrView);
@@ -562,6 +543,46 @@ public class SwipeHelper implements Gefingerpoken {
        return (int) (mFalsingThreshold * factor);
    }

    private float getMaxVelocity() {
        return MAX_DISMISS_VELOCITY * mDensityScale;
    }

    protected float getEscapeVelocity() {
        return SWIPE_ESCAPE_VELOCITY * mDensityScale;
    }

    protected boolean swipedFarEnough() {
        float translation = getTranslation(mCurrView);
        return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
    }

    protected boolean isDismissGesture(MotionEvent ev) {
        boolean falsingDetected = mCallback.isAntiFalsingNeeded();
        if (mFalsingManager.isClassiferEnabled()) {
            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
        } else {
            falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
        }
        return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
                && ev.getActionMasked() == MotionEvent.ACTION_UP
                && mCallback.canChildBeDismissed(mCurrView);
    }

    protected boolean swipedFastEnough() {
        float velocity = getVelocity(mVelocityTracker);
        float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
        float translation = getTranslation(mCurrView);
        boolean ret = (Math.abs(velocity) > getEscapeVelocity()) &&
                (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
                (velocity > 0) == (translation > 0);
        return ret;
    }

    protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
            float translation) {
        return false;
    }

    public interface Callback {
        View getChildAtPosition(MotionEvent ev);

+62 −22
Original line number Diff line number Diff line
@@ -29,11 +29,18 @@ import com.android.systemui.R;

public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {

    private static final int GEAR_ALPHA_ANIM_DURATION = 200;

    public interface SettingsIconRowListener {
        /**
         * Called when the gear behind a notification is touched.
         */
        public void onGearTouched(ExpandableNotificationRow row, int x, int y);

        /**
         * Called when a notification is slid back over the gear.
         */
        public void onSettingsIconRowReset(NotificationSettingsIconRow row);
    }

    private ExpandableNotificationRow mParent;
@@ -45,6 +52,8 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC
    private boolean mSettingsFadedIn = false;
    private boolean mAnimating = false;
    private boolean mOnLeft = true;
    private boolean mDismissing = false;
    private boolean mSnapping = false;
    private int[] mGearLocation = new int[2];
    private int[] mParentLocation = new int[2];

@@ -78,8 +87,14 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC

    public void resetState() {
        setGearAlpha(0f);
        mSettingsFadedIn = false;
        mAnimating = false;
        mSnapping = false;
        mDismissing = false;
        setIconLocation(true /* on left */);
        if (mListener != null) {
            mListener.onSettingsIconRowReset(this);
        }
    }

    public void setGearListener(SettingsIconRowListener listener) {
@@ -94,19 +109,23 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC
        return mParent;
    }

    private void setGearAlpha(float alpha) {
    public void setGearAlpha(float alpha) {
        if (alpha == 0) {
            mSettingsFadedIn = false; // Can fade in again once it's gone.
            setVisibility(View.INVISIBLE);
        } else {
            if (alpha == 1) {
                mSettingsFadedIn = true;
            }
            setVisibility(View.VISIBLE);
        }
        mGearIcon.setAlpha(alpha);
    }

    /**
     * Returns whether the icon is on the left side of the view or not.
     */
    public boolean isIconOnLeft() {
        return mOnLeft;
    }

    /**
     * Returns the horizontal space in pixels required to display the gear behind a notification.
     */
@@ -119,7 +138,7 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC
     * if entire view is visible.
     */
    public boolean isVisible() {
        return mSettingsFadedIn;
        return mGearIcon.getAlpha() > 0;
    }

    public void cancelFadeAnimator() {
@@ -129,16 +148,18 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC
    }

    public void updateSettingsIcons(final float transX, final float size) {
        if (mAnimating || (mGearIcon.getAlpha() == 0)) {
            // Don't adjust when animating or settings aren't visible
        if (mAnimating || !mSettingsFadedIn) {
            // Don't adjust when animating, or if the gear hasn't been shown yet.
            return;
        }
        setIconLocation(transX > 0 /* fromLeft */);

        final float fadeThreshold = size * 0.3f;
        final float absTrans = Math.abs(transX);
        float desiredAlpha = 0;

        if (absTrans <= fadeThreshold) {
        if (absTrans == 0) {
            desiredAlpha = 0;
        } else if (absTrans <= fadeThreshold) {
            desiredAlpha = 1;
        } else {
            desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
@@ -148,6 +169,12 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC

    public void fadeInSettings(final boolean fromLeft, final float transX,
            final float notiThreshold) {
        if (mDismissing || mAnimating) {
            return;
        }
        if (isIconLocationChange(transX)) {
            setGearAlpha(0f);
        }
        setIconLocation(transX > 0 /* fromLeft */);
        mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
        mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -164,40 +191,53 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC
        });
        mFadeAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                mAnimating = false;
                mSettingsFadedIn = false;
            public void onAnimationStart(Animator animation) {
                mAnimating = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                mAnimating = true;
            public void onAnimationCancel(Animator animation) {
                // TODO should animate back to 0f from current alpha
                mGearIcon.setAlpha(0f);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mAnimating = false;
                mSettingsFadedIn = true;
                mSettingsFadedIn = mGearIcon.getAlpha() == 1;
            }
        });
        mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
        mFadeAnimator.setDuration(200);
        mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
        mFadeAnimator.start();
    }

    private void setIconLocation(boolean onLeft) {
        if (onLeft == mOnLeft) {
    public void setIconLocation(boolean onLeft) {
        if (onLeft == mOnLeft || mSnapping) {
            // Same side? Do nothing.
            return;
        }

        setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
        mOnLeft = onLeft;
    }

    public boolean isIconLocationChange(float translation) {
        boolean onLeft = translation > mGearIcon.getPaddingStart();
        boolean onRight = translation < -mGearIcon.getPaddingStart();
        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
            return true;
        }
        return false;
    }

    public void setDismissing() {
        mDismissing = true;
    }

    public void setSnapping(boolean snapping) {
        mSnapping = snapping;
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.gear_icon) {
+150 −43
Original line number Diff line number Diff line
@@ -371,6 +371,11 @@ public class NotificationStackScrollLayout extends ViewGroup
        }
    }

    @Override
    public void onSettingsIconRowReset(NotificationSettingsIconRow row) {
        mSwipeHelper.setSnappedToGear(false);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
@@ -717,12 +722,16 @@ public class NotificationStackScrollLayout extends ViewGroup
            mDragAnimPendingChildren.remove(animView);
        }

        if (targetLeft == 0 && mCurrIconRow != null) {
        if (mCurrIconRow != null) {
            if (targetLeft == 0) {
                mCurrIconRow.resetState();
                mCurrIconRow = null;
                if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
                    mGearExposedView = null;
                }
            } else {
                mSwipeHelper.setSnappedToGear(true);
            }
        }
    }

@@ -3379,15 +3388,11 @@ public class NotificationStackScrollLayout extends ViewGroup
    }

    private class NotificationSwipeHelper extends SwipeHelper {
        private static final int MOVE_STATE_LEFT = -1;
        private static final int MOVE_STATE_UNDEFINED = 0;
        private static final int MOVE_STATE_RIGHT = 1;

        private static final long GEAR_SHOW_DELAY = 60;

        private CheckForDrag mCheckForDrag;
        private Handler mHandler;
        private int mMoveState = MOVE_STATE_UNDEFINED;
        private boolean mGearSnappedTo;
        private boolean mGearSnappedOnLeft;

        public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
            super(swipeDirection, callback, context);
@@ -3400,6 +3405,10 @@ public class NotificationStackScrollLayout extends ViewGroup
            mTranslatingParentView = currView;

            // Reset check for drag gesture
            cancelCheckForDrag();
            if (mCurrIconRow != null) {
                mCurrIconRow.setSnapping(false);
            }
            mCheckForDrag = null;
            mCurrIconRow = null;

@@ -3411,17 +3420,32 @@ public class NotificationStackScrollLayout extends ViewGroup
                mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
                mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
            }
            mMoveState = MOVE_STATE_UNDEFINED;
        }

        @Override
        public void onMoveUpdate(View view, float translation, float delta) {
            final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
            if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
                // Changed directions, make sure we check for drag again.
            if (mCurrIconRow != null) {
                mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.

                // If the gear is visible and the movement is towards it it's not a location change.
                boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
                boolean locationChange = isTowardsGear(translation, onLeft)
                        ? false : mCurrIconRow.isIconLocationChange(translation);
                if (locationChange) {
                    // Don't consider it "snapped" if location has changed.
                    setSnappedToGear(false);

                    // Changed directions, make sure we check to fade in icon again.
                    if (!mHandler.hasCallbacks(mCheckForDrag)) {
                        // No check scheduled, set null to schedule a new one.
                        mCheckForDrag = null;
                    } else {
                        // Check scheduled, reset alpha and update location; check will fade it in
                        mCurrIconRow.setGearAlpha(0f);
                        mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
                    }
                }
            }
            mMoveState = newMoveState;

            final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
                    && ((ExpandableNotificationRow) view).areGutsExposed();
@@ -3434,35 +3458,101 @@ public class NotificationStackScrollLayout extends ViewGroup

        @Override
        public void dismissChild(final View view, float velocity) {
            cancelCheckForDrag();
            super.dismissChild(view, velocity);
            cancelCheckForDrag();
            setSnappedToGear(false);
        }

        @Override
        public void snapChild(final View animView, final float targetLeft, float velocity) {
            final float snapBackThreshold = getSpaceForGear(animView);
            final float translation = getTranslation(animView);
            final boolean fromLeft = translation > 0;
            final float absTrans = Math.abs(translation);
            final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
            super.snapChild(animView, targetLeft, velocity);
            onDragCancelled(animView);
            if (targetLeft == 0) {
                cancelCheckForDrag();
                setSnappedToGear(false);
            }
        }

            boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
                    && translation <= notiThreshold) ||
                    (!fromLeft && absTrans >= snapBackThreshold * 0.4f
                            && absTrans <= notiThreshold);

            if (pastGear && !isPinnedHeadsUp(animView)
                    && (animView instanceof ExpandableNotificationRow)) {
                // bouncity
                final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
        @Override
        public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
                float translation) {
            if (mCurrIconRow == null) {
                cancelCheckForDrag();
                return false; // Let SwipeHelper handle it.
            }

            boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
            boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();

            if (mGearSnappedTo && mCurrIconRow.isVisible()) {
                if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
                    boolean coveringGear =
                            Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
                    if (gestureTowardsGear || coveringGear) {
                        // Gesture is towards or covering the gear
                        snapChild(animView, 0 /* leftTarget */, velocity);
                    } else if (isDismissGesture(ev)) {
                        // Gesture is a dismiss that's not towards the gear
                        dismissChild(animView, swipedFastEnough() ? velocity : 0f);
                    } else {
                        // Didn't move enough to dismiss or cover, snap to the gear
                        snapToGear(animView, velocity);
                    }
                } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
                        || (gestureTowardsGear && !swipedFarEnough())) {
                    // The gear has been snapped to previously, however, the gear is now on the
                    // other side. If gesture is towards gear and not too far snap to the gear.
                    snapToGear(animView, velocity);
                } else {
                    dismissOrSnapBack(animView, velocity, ev);
                }
            } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
                    || gestureTowardsGear) {
                // Gear has not been snapped to previously and this is gear revealing gesture
                snapToGear(animView, velocity);
            } else {
                dismissOrSnapBack(animView, velocity, ev);
            }
            return true;
        }

        private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
            if (isDismissGesture(ev)) {
                dismissChild(animView, swipedFastEnough() ? velocity : 0f);
            } else {
                snapChild(animView, 0 /* leftTarget */, velocity);
            }
        }

        private void snapToGear(View animView, float velocity) {
            final float snapBackThreshold = getSpaceForGear(animView);
            final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
                    : -snapBackThreshold;
            mGearExposedView = mTranslatingParentView;
                if (mGearDisplayedListener != null) {
            if (mGearDisplayedListener != null
                    && (animView instanceof ExpandableNotificationRow)) {
                mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
            }
            if (mCurrIconRow != null) {
                mCurrIconRow.setSnapping(true);
                setSnappedToGear(true);
            }
            onDragCancelled(animView);
            super.snapChild(animView, target, velocity);
            } else {
                super.snapChild(animView, 0, velocity);
        }

        private boolean swipedEnoughToShowGear(View animView) {
            final float snapBackThreshold = getSpaceForGear(animView);
            final float translation = getTranslation(animView);
            final boolean fromLeft = translation > 0;
            final float absTrans = Math.abs(translation);
            final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;

            // If the notification can't be dismissed then how far it can move is
            // restricted -- reduce the distance it needs to move in this case.
            final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
            return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold;
        }

        @Override
@@ -3498,6 +3588,25 @@ public class NotificationStackScrollLayout extends ViewGroup
            }
        }

        /**
         * Returns whether the gesture is towards the gear location or not.
         */
        private boolean isTowardsGear(float velocity, boolean onLeft) {
            if (mCurrIconRow == null) {
                return false;
            }
            return mCurrIconRow.isVisible()
                    && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
        }

        /**
         * Indicates the the gear has been snapped to.
         */
        private void setSnappedToGear(boolean snapped) {
            mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
            mGearSnappedTo = snapped && mCurrIconRow != null;
        }

        /**
         * Returns the horizontal space in pixels required to display the gear behind a
         * notification.
@@ -3510,7 +3619,7 @@ public class NotificationStackScrollLayout extends ViewGroup
        }

        private void checkForDrag() {
            if (mCheckForDrag == null) {
            if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
                mCheckForDrag = new CheckForDrag();
                mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
            }
@@ -3521,7 +3630,6 @@ public class NotificationStackScrollLayout extends ViewGroup
                mCurrIconRow.cancelFadeAnimator();
            }
            mHandler.removeCallbacks(mCheckForDrag);
            mCheckForDrag = null;
        }

        private final class CheckForDrag implements Runnable {
@@ -3531,14 +3639,13 @@ public class NotificationStackScrollLayout extends ViewGroup
                final float absTransX = Math.abs(translation);
                final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
                final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
                if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
                if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
                        || mCurrIconRow.isIconLocationChange(translation)))
                        && absTransX >= bounceBackToGearWidth * 0.4
                        && absTransX < notiThreshold) {
                    // Show icon
                    // Fade in the gear
                    mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
                            notiThreshold);
                } else {
                    // Allow more to be posted if this wasn't a drag.
                    mCheckForDrag = null;
                }
            }
        }
@@ -3551,7 +3658,7 @@ public class NotificationStackScrollLayout extends ViewGroup

            final View prevGearExposedView = mGearExposedView;
            mGearExposedView = null;

            mGearSnappedTo = false;
            Animator anim = getViewTranslationAnimator(prevGearExposedView,
                    0 /* leftTarget */, null /* updateListener */);
            if (anim != null) {