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

Commit 267137fd authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

Long-press not required for mouse or stylus.

When interacting with lock screen shortcuts, a long-press is required,
mainly to help protect against false clicks (e.g. "falsing"). When using
a stylus pen or a mouse, it is no longer a requirement, as those pointer types offer a  much
greater degree of confidence that the click was not a false click.

This CL takes care of it by checking the type of pointer and, if it's a
stylus or a mouse, allowing a simple click gesture to happen, instead of the
complicated long-press.

Fix: 267534467
Test: manually tested with a stylus - simple "launch" affordances like
camera work and require a single tap. Complex "toggle" affordances like
do not disturb toggle on and off, requiring a single tap
Test: manually tested without a stylus. "Launch" and "toggle"
affordances still require a long-press, the animation still works. When
tapping them with a short click, the shake animation/vibration and
educational text still appear as expected.

Change-Id: I92270e554f41b0066f5e8c8fec52c5d7c6c93b29
parent 4f197f9a
Loading
Loading
Loading
Loading
+122 −61
Original line number Original line Diff line number Diff line
@@ -381,6 +381,13 @@ object KeyguardBottomAreaViewBinder {
            return when (event?.actionMasked) {
            return when (event?.actionMasked) {
                MotionEvent.ACTION_DOWN ->
                MotionEvent.ACTION_DOWN ->
                    if (viewModel.configKey != null) {
                    if (viewModel.configKey != null) {
                        if (isUsingAccurateTool(event)) {
                            // For accurate tool types (stylus, mouse, etc.), we don't require a
                            // long-press.
                        } else {
                            // When not using a stylus, we require a long-press to activate the
                            // quick affordance, mostly to do "falsing" (e.g. protect from false
                            // clicks in the pocket/bag).
                            longPressAnimator =
                            longPressAnimator =
                                view
                                view
                                    .animate()
                                    .animate()
@@ -388,43 +395,40 @@ object KeyguardBottomAreaViewBinder {
                                    .scaleY(PRESSED_SCALE)
                                    .scaleY(PRESSED_SCALE)
                                    .setDuration(longPressDurationMs)
                                    .setDuration(longPressDurationMs)
                                    .withEndAction {
                                    .withEndAction {
                                    view.setOnClickListener {
                                        dispatchClick(viewModel.configKey)
                                        vibratorHelper?.vibrate(
                                            if (viewModel.isActivated) {
                                                Vibrations.Activated
                                            } else {
                                                Vibrations.Deactivated
                                            }
                                        )
                                        viewModel.onClicked(
                                            KeyguardQuickAffordanceViewModel.OnClickedParameters(
                                                configKey = viewModel.configKey,
                                                expandable = Expandable.fromView(view),
                                            )
                                        )
                                    }
                                    view.performClick()
                                    view.setOnClickListener(null)
                                        cancel()
                                        cancel()
                                    }
                                    }
                        }
                        true
                        true
                    } else {
                    } else {
                        false
                        false
                    }
                    }
                MotionEvent.ACTION_MOVE -> {
                MotionEvent.ACTION_MOVE -> {
                    if (event.historySize > 0) {
                    if (!isUsingAccurateTool(event)) {
                        val distance =
                        // Moving too far while performing a long-press gesture cancels that
                            sqrt(
                        // gesture.
                                (event.y - event.getHistoricalY(0)).pow(2) +
                        val distanceMoved = distanceMoved(event)
                                    (event.x - event.getHistoricalX(0)).pow(2)
                        if (distanceMoved > ViewConfiguration.getTouchSlop()) {
                            )
                        if (distance > ViewConfiguration.getTouchSlop()) {
                            cancel()
                            cancel()
                        }
                        }
                    }
                    }
                    true
                    true
                }
                }
                MotionEvent.ACTION_UP -> {
                MotionEvent.ACTION_UP -> {
                    if (isUsingAccurateTool(event)) {
                        // When using an accurate tool type (stylus, mouse, etc.), we don't require
                        // a long-press gesture to activate the quick affordance. Therefore, lifting
                        // the pointer performs a click.
                        if (
                            viewModel.configKey != null &&
                                distanceMoved(event) <= ViewConfiguration.getTouchSlop()
                        ) {
                            dispatchClick(viewModel.configKey)
                        }
                    } else {
                        // When not using a stylus, lifting the finger/pointer will actually cancel
                        // the long-press gesture. Calling cancel after the quick affordance was
                        // already long-press activated is a no-op, so it's safe to call from here.
                        cancel(
                        cancel(
                            onAnimationEnd =
                            onAnimationEnd =
                                if (event.eventTime - event.downTime < longPressDurationMs) {
                                if (event.eventTime - event.downTime < longPressDurationMs) {
@@ -457,6 +461,7 @@ object KeyguardBottomAreaViewBinder {
                                    null
                                    null
                                }
                                }
                        )
                        )
                    }
                    true
                    true
                }
                }
                MotionEvent.ACTION_CANCEL -> {
                MotionEvent.ACTION_CANCEL -> {
@@ -467,6 +472,28 @@ object KeyguardBottomAreaViewBinder {
            }
            }
        }
        }


        private fun dispatchClick(
            configKey: String,
        ) {
            view.setOnClickListener {
                vibratorHelper?.vibrate(
                    if (viewModel.isActivated) {
                        Vibrations.Activated
                    } else {
                        Vibrations.Deactivated
                    }
                )
                viewModel.onClicked(
                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
                        configKey = configKey,
                        expandable = Expandable.fromView(view),
                    )
                )
            }
            view.performClick()
            view.setOnClickListener(null)
        }

        private fun cancel(onAnimationEnd: Runnable? = null) {
        private fun cancel(onAnimationEnd: Runnable? = null) {
            longPressAnimator?.cancel()
            longPressAnimator?.cancel()
            longPressAnimator = null
            longPressAnimator = null
@@ -475,6 +502,40 @@ object KeyguardBottomAreaViewBinder {


        companion object {
        companion object {
            private const val PRESSED_SCALE = 1.5f
            private const val PRESSED_SCALE = 1.5f

            /**
             * Returns `true` if the tool type at the given pointer index is an accurate tool (like
             * stylus or mouse), which means we can trust it to not be a false click; `false`
             * otherwise.
             */
            private fun isUsingAccurateTool(
                event: MotionEvent,
                pointerIndex: Int = 0,
            ): Boolean {
                return when (event.getToolType(pointerIndex)) {
                    MotionEvent.TOOL_TYPE_STYLUS -> true
                    MotionEvent.TOOL_TYPE_MOUSE -> true
                    else -> false
                }
            }

            /**
             * Returns the amount of distance the pointer moved since the historical record at the
             * [since] index.
             */
            private fun distanceMoved(
                event: MotionEvent,
                since: Int = 0,
            ): Float {
                return if (event.historySize > 0) {
                    sqrt(
                        (event.y - event.getHistoricalY(since)).pow(2) +
                            (event.x - event.getHistoricalX(since)).pow(2)
                    )
                } else {
                    0f
                }
            }
        }
        }
    }
    }