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

Commit d0814293 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Allowing clicks that can occur during the long-press effect.

The long-press effect now respects the detection of taps/clicks when an
ACTION_UP occurs during the animation of the long-press. This is more
consistent with the older behavior of the tiles, where clicks could
occur regardless of the time between the ACTION_DOWN and ACTION_UP
events. This solves the problem of inconsistent tap detections compared
to the previous behavior. For a graceful transition, the clicks are
performed after the long-press effect has finished reversing.

Test: atest SystemUIRoboTests:QSLongPressEffectTest
Flag: com.android.systemui.quick_settings_visual_haptics_longpress
Bug: 342831659
Change-Id: I4ce853a8d9113bc9be3789b7ad5bfd80b7a51120
parent c7529cb8
Loading
Loading
Loading
Loading
+40 −18
Original line number Diff line number Diff line
@@ -144,7 +144,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
            longPressEffect.handleActionUp()

            // THEN the effect reverses
            assertEffectReverses()
            assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP)
        }

    @Test
@@ -171,18 +171,17 @@ class QSLongPressEffectTest : SysuiTestCase() {
            longPressEffect.handleActionCancel()

            // THEN the effect gets reversed
            assertEffectReverses()
            assertEffectReverses(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
        }

    @Test
    fun onAnimationComplete_keyguardDismissible_effectEndsWithPrepare() =
    fun onAnimationComplete_keyguardDismissible_effectCompletes() =
        testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
            // GIVEN that the animation completes
            longPressEffect.handleAnimationComplete()

            // THEN the long-press effect completes and the view is called to prepare
            // THEN the long-press effect completes
            assertEffectCompleted()
            verify(callback, times(1)).onPrepareForLaunch()
        }

    @Test
@@ -199,6 +198,26 @@ class QSLongPressEffectTest : SysuiTestCase() {
            verify(callback, times(1)).onResetProperties()
        }

    @Test
    fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() =
        testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
            // GIVEN that the animation completes
            longPressEffect.handleAnimationComplete()

            // THEN the callback for finished reversing is used.
            verify(callback, times(1)).onEffectFinishedReversing()
        }

    @Test
    fun onAnimationComplete_whenRunningBackwardsFromCancel_endsInIdle() =
        testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
            // GIVEN that the animation completes
            longPressEffect.handleAnimationComplete()

            // THEN the effect ends in the idle state.
            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
        }

    @Test
    fun onActionDown_whileRunningBackwards_cancels() =
        testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
@@ -223,11 +242,8 @@ class QSLongPressEffectTest : SysuiTestCase() {
        }

    @Test
    fun onAnimationComplete_whileRunningBackwards_goesToIdle() =
        testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS) {
            // GIVEN an action cancel occurs and the effect gets reversed
            longPressEffect.handleActionCancel()

    fun onAnimationComplete_whileRunningBackwardsFromCancel_goesToIdle() =
        testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) {
            // GIVEN that the animation completes
            longPressEffect.handleAnimationComplete()

@@ -307,12 +323,16 @@ class QSLongPressEffectTest : SysuiTestCase() {
    /**
     * Asserts that the effect did not start by checking that:
     * 1. No haptics are played
     * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
     *    [QSLongPressEffect.State.RUNNING_FORWARD]
     * 2. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP] or
     *    [QSLongPressEffect.State.RUNNING_FORWARD] or
     *    [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL]
     */
    private fun assertEffectDidNotStart() {
        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
        assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
        assertThat(longPressEffect.state)
            .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP)
        assertThat(longPressEffect.state)
            .isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL)
        assertThat(vibratorHelper.totalVibrations).isEqualTo(0)
    }

@@ -330,12 +350,14 @@ class QSLongPressEffectTest : SysuiTestCase() {
    }

    /**
     * Assert that the effect gets reverted by checking that:
     * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
     * 2. An action to reverse the animator is emitted
     * Assert that the effect gets reverted by checking that the callback to reverse the animator is
     * used, and that the state is given reversing state.
     *
     * @param[reversingState] Either [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL] or
     *   [QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP]
     */
    private fun assertEffectReverses() {
        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
    private fun assertEffectReverses(reversingState: QSLongPressEffect.State) {
        assertThat(longPressEffect.state).isEqualTo(reversingState)
        verify(callback, times(1)).onReverseAnimator()
    }
}
+29 −21
Original line number Diff line number Diff line
@@ -97,14 +97,15 @@ constructor(
            State.IDLE -> {
                setState(State.TIMEOUT_WAIT)
            }
            State.RUNNING_BACKWARDS -> callback?.onCancelAnimator()
            State.RUNNING_BACKWARDS_FROM_UP,
            State.RUNNING_BACKWARDS_FROM_CANCEL -> callback?.onCancelAnimator()
            else -> {}
        }
    }

    fun handleActionUp() {
        if (state == State.RUNNING_FORWARD) {
            setState(State.RUNNING_BACKWARDS)
            setState(State.RUNNING_BACKWARDS_FROM_UP)
            callback?.onReverseAnimator()
        }
    }
@@ -113,7 +114,7 @@ constructor(
        when (state) {
            State.TIMEOUT_WAIT -> setState(State.IDLE)
            State.RUNNING_FORWARD -> {
                setState(State.RUNNING_BACKWARDS)
                setState(State.RUNNING_BACKWARDS_FROM_CANCEL)
                callback?.onReverseAnimator()
            }
            else -> {}
@@ -127,20 +128,24 @@ constructor(

    /** This function is called both when an animator completes or gets cancelled */
    fun handleAnimationComplete() {
        if (state == State.RUNNING_FORWARD) {
        when (state) {
            State.RUNNING_FORWARD -> {
                setState(State.IDLE)
                vibrate(snapEffect)
                if (keyguardStateController.isUnlocked) {
                callback?.onPrepareForLaunch()
                    qsTile?.longClick(expandable)
                } else {
                    callback?.onResetProperties()
                    qsTile?.longClick(expandable)
                }
            }
        if (state != State.TIMEOUT_WAIT) {
            // This will happen if the animator did not finish by being cancelled
            State.RUNNING_BACKWARDS_FROM_UP -> {
                setState(State.IDLE)
                callback?.onEffectFinishedReversing()
                qsTile?.click(expandable)
            }
            State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
            else -> {}
        }
    }

@@ -191,20 +196,23 @@ constructor(

    enum class State {
        IDLE, /* The effect is idle waiting for touch input */
        TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
        TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
        RUNNING_FORWARD, /* The effect is running normally */
        RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
        /* The effect was interrupted by an ACTION_UP and is now running backwards */
        RUNNING_BACKWARDS_FROM_UP,
        /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
        RUNNING_BACKWARDS_FROM_CANCEL,
    }

    /** Callbacks to notify view and animator actions */
    interface Callback {

        /** Prepare for an activity launch */
        fun onPrepareForLaunch()

        /** Reset the tile visual properties */
        fun onResetProperties()

        /** Event where the effect completed by being reversed */
        fun onEffectFinishedReversing()

        /** Start the effect animator */
        fun onStartAnimator()

+15 −4
Original line number Diff line number Diff line
@@ -350,6 +350,10 @@ constructor(

        initialLongPressProperties?.width = width
        finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * width

        val deltaW = (LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * width
        paddingForLaunch.left = -deltaW.toInt() / 2
        paddingForLaunch.right = deltaW.toInt() / 2
    }

    private fun maybeUpdateLongPressEffectHeight(height: Float) {
@@ -357,6 +361,10 @@ constructor(

        initialLongPressProperties?.height = height
        finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * height

        val deltaH = (LONG_PRESS_EFFECT_HEIGHT_SCALE - 1f) * height
        paddingForLaunch.top = -deltaH.toInt() / 2
        paddingForLaunch.bottom = deltaH.toInt() / 2
    }

    override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
@@ -432,14 +440,16 @@ constructor(
        longPressEffect?.callback =
            object : QSLongPressEffect.Callback {

                override fun onPrepareForLaunch() {
                    prepareForLaunch()
                }

                override fun onResetProperties() {
                    resetLongPressEffectProperties()
                }

                override fun onEffectFinishedReversing() {
                    // The long-press effect properties finished at the same starting point.
                    // This is the same as if the properties were reset
                    haveLongPressPropertiesBeenReset = true
                }

                override fun onStartAnimator() {
                    if (longPressEffectAnimator?.isRunning != true) {
                        longPressEffectAnimator =
@@ -1043,6 +1053,7 @@ constructor(
                getOverlayColorForState(Tile.STATE_ACTIVE),
                Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
            )
        prepareForLaunch()
    }

    private fun changeCornerRadius(radius: Float) {