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

Commit 9687d080 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez Committed by Android (Google) Code Review
Browse files

Merge "Revert "Letting View handle click and longclick detection"" into main

parents 96dc5cc6 0edf9f03
Loading
Loading
Loading
Loading
+16 −32
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -42,7 +41,6 @@ class QSLongPressEffectTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val vibratorHelper = kosmos.vibratorHelper
    private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")

    private val effectDuration = 400
    private val lowTickDuration = 12
@@ -63,7 +61,6 @@ class QSLongPressEffectTest : SysuiTestCase() {
                vibratorHelper,
                kosmos.keyguardInteractor,
            )
        longPressEffect.qsTile = qsTile
    }

    @Test
@@ -94,10 +91,8 @@ class QSLongPressEffectTest : SysuiTestCase() {
        // GIVEN an action down event occurs
        longPressEffect.handleActionDown()

        // THEN the effect moves to the TIMEOUT_WAIT state and starts the wait
        val action by collectLastValue(longPressEffect.actionType)
        // THEN the effect moves to the TIMEOUT_WAIT state
        assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
    }

    @Test
@@ -111,6 +106,20 @@ class QSLongPressEffectTest : SysuiTestCase() {
            assertEffectDidNotStart()
        }

    @Test
    fun onActionUp_whileWaiting_performsClick() =
        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
            // GIVEN an action is being collected
            val action by collectLastValue(longPressEffect.actionType)

            // GIVEN an action up occurs
            longPressEffect.handleActionUp()

            // THEN the action to invoke is the click action and the effect does not start
            assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
            assertEffectDidNotStart()
        }

    @Test
    fun onWaitComplete_whileWaiting_beginsEffect() =
        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
@@ -212,10 +221,8 @@ class QSLongPressEffectTest : SysuiTestCase() {
            // GIVEN that the animator was cancelled
            longPressEffect.handleAnimationCancel()

            // THEN the state goes to the timeout wait and the wait is posted
            val action by collectLastValue(longPressEffect.actionType)
            // THEN the state goes to the timeout wait
            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
            assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
        }

    @Test
@@ -231,29 +238,6 @@ class QSLongPressEffectTest : SysuiTestCase() {
            assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
        }

    @Test
    fun onTileClick_whileWaiting_withQSTile_clicks() =
        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
            // GIVEN that a click was detected
            val couldClick = longPressEffect.onTileClick()

            // THEN the click is successful
            assertThat(couldClick).isTrue()
        }

    @Test
    fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
            // GIVEN that no QSTile has been set
            longPressEffect.qsTile = null

            // GIVEN that a click was detected
            val couldClick = longPressEffect.onTileClick()

            // THEN the click is not successful
            assertThat(couldClick).isFalse()
        }

    private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
        with(kosmos) {
            testScope.runTest {
+18 −37
Original line number Diff line number Diff line
@@ -19,9 +19,7 @@ package com.android.systemui.haptics.qs
import android.os.VibrationEffect
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.animation.Expandable
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -53,10 +51,6 @@ constructor(
    var state = State.IDLE
        private set

    /** The QSTile and Expandable used to perform a long-click and click actions */
    var qsTile: QSTile? = null
    var expandable: Expandable? = null

    /** Flow for view control and action */
    private val _postedActionType = MutableStateFlow<ActionType?>(null)
    val actionType: Flow<ActionType?> =
@@ -111,7 +105,6 @@ constructor(
        when (state) {
            State.IDLE -> {
                setState(State.TIMEOUT_WAIT)
                _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
            }
            State.RUNNING_BACKWARDS -> _postedActionType.value = ActionType.CANCEL_ANIMATOR
            else -> {}
@@ -119,17 +112,23 @@ constructor(
    }

    fun handleActionUp() {
        if (state == State.RUNNING_FORWARD) {
        when (state) {
            State.TIMEOUT_WAIT -> {
                _postedActionType.value = ActionType.CLICK
                setState(State.IDLE)
            }
            State.RUNNING_FORWARD -> {
                _postedActionType.value = ActionType.REVERSE_ANIMATOR
                setState(State.RUNNING_BACKWARDS)
            }
            else -> {}
        }
    }

    fun handleActionCancel() {
        when (state) {
            State.TIMEOUT_WAIT -> {
                setState(State.IDLE)
                clearActionType()
            }
            State.RUNNING_FORWARD -> {
                _postedActionType.value = ActionType.REVERSE_ANIMATOR
@@ -146,23 +145,18 @@ constructor(

    /** This function is called both when an animator completes or gets cancelled */
    fun handleAnimationComplete() {
        when (state) {
            State.RUNNING_FORWARD -> {
                setState(State.IDLE)
        if (state == State.RUNNING_FORWARD) {
            vibrate(snapEffect)
            _postedActionType.value = ActionType.LONG_PRESS
        }
            State.RUNNING_BACKWARDS -> {
        if (state != State.TIMEOUT_WAIT) {
            // This will happen if the animator did not finish by being cancelled
            setState(State.IDLE)
                clearActionType()
            }
            else -> {}
        }
    }

    fun handleAnimationCancel() {
        setState(State.TIMEOUT_WAIT)
        _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
    }

    fun handleTimeoutComplete() {
@@ -196,22 +190,9 @@ constructor(
                effectDuration
            )
        setState(State.IDLE)
        clearActionType()
        return true
    }

    fun onTileClick(): Boolean {
        if (state == State.TIMEOUT_WAIT) {
            setState(State.IDLE)
            clearActionType()
            qsTile?.let {
                it.click(expandable)
                return true
            }
        }
        return false
    }

    enum class State {
        IDLE, /* The effect is idle waiting for touch input */
        TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
@@ -221,7 +202,7 @@ constructor(

    /* A type of action to perform on the view depending on the effect's state and logic */
    enum class ActionType {
        WAIT_TAP_TIMEOUT,
        CLICK,
        LONG_PRESS,
        RESET_AND_LONG_PRESS,
        START_ANIMATOR,
+28 −6
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.haptics.qs

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.doOnCancel
@@ -28,7 +30,6 @@ import com.android.app.tracing.coroutines.launch
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filterNotNull

object QSLongPressEffectViewBinder {
@@ -40,6 +41,9 @@ object QSLongPressEffectViewBinder {
    ): DisposableHandle? {
        if (qsLongPressEffect == null) return null

        // Set the touch listener as the long-press effect
        setTouchListener(tile, qsLongPressEffect)

        return tile.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                // Action to perform
@@ -48,18 +52,18 @@ object QSLongPressEffectViewBinder {

                    qsLongPressEffect.actionType.filterNotNull().collect { action ->
                        when (action) {
                            QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT -> {
                                delay(ViewConfiguration.getTapTimeout().toLong())
                                qsLongPressEffect.handleTimeoutComplete()
                            QSLongPressEffect.ActionType.CLICK -> {
                                tile.performClick()
                                qsLongPressEffect.clearActionType()
                            }
                            QSLongPressEffect.ActionType.LONG_PRESS -> {
                                tile.prepareForLaunch()
                                qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
                                tile.performLongClick()
                                qsLongPressEffect.clearActionType()
                            }
                            QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
                                tile.resetLongPressEffectProperties()
                                qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
                                tile.performLongClick()
                                qsLongPressEffect.clearActionType()
                            }
                            QSLongPressEffect.ActionType.START_ANIMATOR -> {
@@ -102,4 +106,22 @@ object QSLongPressEffectViewBinder {
            }
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
        tile.setOnTouchListener { _, event ->
            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    tile.postDelayed(
                        { longPressEffect?.handleTimeoutComplete() },
                        ViewConfiguration.getTapTimeout().toLong(),
                    )
                    longPressEffect?.handleActionDown()
                }
                MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
                MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
            }
            true
        }
    }
}
+6 −28
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.qs.tileimpl
import android.animation.ArgbEvaluator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
@@ -38,7 +37,6 @@ import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
@@ -397,22 +395,15 @@ open class QSTileViewImpl @JvmOverloads constructor(

    override fun init(tile: QSTile) {
        val expandable = Expandable.fromView(this)
        if (quickSettingsVisualHapticsLongpress()) {
            isHapticFeedbackEnabled = false
            longPressEffect?.qsTile = tile
            longPressEffect?.expandable = expandable
            init(
                { _: View? -> longPressEffect?.onTileClick() },
                null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
            )
        } else {
        init(
                { _: View? -> tile.click(expandable) },
                { _: View? ->
                    tile.longClick(expandable)
                    true
                },
                }
        )
        if (quickSettingsVisualHapticsLongpress()) {
            isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
        }
    }

@@ -550,20 +541,6 @@ open class QSTileViewImpl @JvmOverloads constructor(
        return sb.toString()
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        // let the View run the onTouch logic for click and long-click detection
        val result = super.onTouchEvent(event)
        if (longPressEffect != null) {
            when (event?.actionMasked) {
                MotionEvent.ACTION_DOWN -> longPressEffect.handleActionDown()
                MotionEvent.ACTION_UP -> longPressEffect.handleActionUp()
                MotionEvent.ACTION_CANCEL -> longPressEffect.handleActionCancel()
            }
        }
        return result
    }

    // HANDLE STATE CHANGES RELATED METHODS

    protected open fun handleStateChanged(state: QSTile.State) {
@@ -698,6 +675,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
            // Long-press effects might have been enabled before but the new state does not
            // handle a long-press. In this case, we go back to the behaviour of a regular tile
            // and clean-up the resources
            setOnTouchListener(null)
            unbindLongPressEffect()
            showRippleEffect = isClickable
            initialLongPressProperties = null
+1 −30
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ package com.android.systemui.qs.tileimpl
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
import android.testing.TestableLooper
import android.text.TextUtils
@@ -30,13 +28,11 @@ import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.qs.QSLongPressEffect
import com.android.systemui.haptics.qs.qsLongPressEffect
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -540,30 +536,10 @@ class QSTileViewImplTest : SysuiTestCase() {
        assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
    }

    @Test
    @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
    fun onInit_withLongPressEffect_longPressEffectHasTileAndExpandable() {
        val tile = kosmos.qsTileFactory.createTile("Test Tile")
        tileView.init(tile)

        assertThat(tileView.isTileAddedToLongPress).isTrue()
        assertThat(tileView.isExpandableAddedToLongPress).isTrue()
    }

    @Test
    @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
    fun onInit_withoutLongPressEffect_longPressEffectDoesNotHaveTileAndExpandable() {
        val tile = kosmos.qsTileFactory.createTile("Test Tile")
        tileView.init(tile)

        assertThat(tileView.isTileAddedToLongPress).isFalse()
        assertThat(tileView.isExpandableAddedToLongPress).isFalse()
    }

    class FakeTileView(
        context: Context,
        collapsed: Boolean,
        private val longPressEffect: QSLongPressEffect?,
        longPressEffect: QSLongPressEffect?,
    ) : QSTileViewImpl(
            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
            collapsed,
@@ -571,11 +547,6 @@ class QSTileViewImplTest : SysuiTestCase() {
    ) {
        var constantLongPressEffectDuration = 500

        val isTileAddedToLongPress: Boolean
            get() = longPressEffect?.qsTile != null
        val isExpandableAddedToLongPress: Boolean
            get() = longPressEffect?.expandable != null

        override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
        fun changeState(state: QSTile.State) {
            handleStateChanged(state)