summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Juan Sebastian Martinez <juansmartinez@google.com> 2024-07-11 03:13:13 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-07-11 03:13:13 +0000
commit17f41e3d6b1e51d3b9643134e2e6e1bd57d601b5 (patch)
tree6060f25f292a773fc88999df7de8d32451990753
parentf048cc66e332ce3c3e8e3bb84199f088b5701c64 (diff)
parent41cb43322bad9915458a083f019a0929854cf6bf (diff)
Merge "Adding click and long-click terminal states." into main
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt3
4 files changed, 138 insertions, 28 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 74eee9b24cbc..693fcdabea58 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -17,10 +17,12 @@
package com.android.systemui.haptics.qs
import android.os.VibrationEffect
+import android.service.quicksettings.Tile
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.qsTileFactory
@@ -69,6 +71,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
+ kosmos.falsingManager,
)
longPressEffect.callback = callback
longPressEffect.qsTile = qsTile
@@ -175,17 +178,17 @@ class QSLongPressEffectTest : SysuiTestCase() {
}
@Test
- fun onAnimationComplete_keyguardDismissible_effectCompletes() =
+ fun onAnimationComplete_keyguardDismissible_effectEndsInLongClicked() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the long-press effect completes
- assertEffectCompleted()
+ // THEN the long-press effect completes with a long-click state
+ assertEffectCompleted(QSLongPressEffect.State.LONG_CLICKED)
}
@Test
- fun onAnimationComplete_keyguardNotDismissible_effectEndsWithReset() =
+ fun onAnimationComplete_keyguardNotDismissible_effectEndsInIdleWithReset() =
testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
// GIVEN that the keyguard is not dismissible
whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
@@ -193,19 +196,20 @@ class QSLongPressEffectTest : SysuiTestCase() {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the long-press effect completes and the properties are called to reset
- assertEffectCompleted()
+ // THEN the long-press effect ends in the idle state and the properties are reset
+ assertEffectCompleted(QSLongPressEffect.State.IDLE)
verify(callback, times(1)).onResetProperties()
}
@Test
- fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversing() =
+ fun onAnimationComplete_whenRunningBackwardsFromUp_endsWithFinishedReversingAndClick() =
testWhileInState(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_UP) {
// GIVEN that the animation completes
longPressEffect.handleAnimationComplete()
- // THEN the callback for finished reversing is used.
+ // THEN the callback for finished reversing is used and the effect ends with a click.
verify(callback, times(1)).onEffectFinishedReversing()
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.CLICKED)
}
@Test
@@ -262,18 +266,88 @@ class QSLongPressEffectTest : SysuiTestCase() {
}
@Test
- fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
- testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
- // GIVEN that no QSTile has been set
- longPressEffect.qsTile = null
-
+ fun onTileClick_whileIdle_withQSTile_clicks() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
// GIVEN that a click was detected
val couldClick = longPressEffect.onTileClick()
+ // THEN the click is successful
+ assertThat(couldClick).isTrue()
+ }
+
+ @Test
+ fun onTileClick_whenBouncerIsShowing_ignoresClick() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
+ // GIVEN that the bouncer is showing
+ whenever(kosmos.keyguardStateController.isPrimaryBouncerShowing).thenReturn(true)
+
+ // WHEN a click is detected by the tile view
+ val couldClick = longPressEffect.onTileClick()
+
// THEN the click is not successful
assertThat(couldClick).isFalse()
}
+ @Test
+ fun getStateForClick_withUnavailableTile_returnsIdle() {
+ // GIVEN an unavailable tile
+ qsTile.state?.state = Tile.STATE_UNAVAILABLE
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ @Test
+ fun getStateForClick_withFalseTapWhenLocked_returnsIdle() {
+ // GIVEN an active tile
+ qsTile.state?.state = Tile.STATE_ACTIVE
+
+ // GIVEN that the device is locked and a false tap is detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(true)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ @Test
+ fun getStateForClick_withValidTapAndTile_returnsClicked() {
+ // GIVEN an active tile
+ qsTile.state?.state = Tile.STATE_ACTIVE
+
+ // GIVEN that the device is locked and a false tap is not detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(false)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is CLICKED
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.CLICKED)
+ }
+
+ @Test
+ fun getStateForClick_withNullTile_returnsIdle() {
+ // GIVEN that the tile is null
+ longPressEffect.qsTile = null
+
+ // GIVEN that the device is locked and a false tap is not detected
+ whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.falsingManager.setFalseTap(false)
+
+ // WHEN determining the state of a click action
+ val clickState = longPressEffect.getStateForClick()
+
+ // THEN the state is IDLE
+ assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
@@ -339,14 +413,14 @@ class QSLongPressEffectTest : SysuiTestCase() {
/**
* Asserts that the effect completes by checking that:
* 1. The final snap haptics are played
- * 2. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 2. The internal state goes back to specified end state.
*/
- private fun assertEffectCompleted() {
+ private fun assertEffectCompleted(endState: QSLongPressEffect.State) {
val snapEffect = LongPressHapticBuilder.createSnapEffect()
assertThat(snapEffect).isNotNull()
assertThat(vibratorHelper.hasVibratedWithEffects(snapEffect!!)).isTrue()
- assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(longPressEffect.state).isEqualTo(endState)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 7b5139aa510e..c44eb471d460 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,8 +17,10 @@
package com.android.systemui.haptics.qs
import android.os.VibrationEffect
+import android.service.quicksettings.Tile
import androidx.annotation.VisibleForTesting
import com.android.systemui.animation.Expandable
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,6 +42,7 @@ class QSLongPressEffect
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
+ private val falsingManager: FalsingManager,
) {
var effectDuration = 0
@@ -130,18 +133,18 @@ constructor(
fun handleAnimationComplete() {
when (state) {
State.RUNNING_FORWARD -> {
- setState(State.IDLE)
vibrate(snapEffect)
if (keyguardStateController.isUnlocked) {
- qsTile?.longClick(expandable)
+ setState(State.LONG_CLICKED)
} else {
callback?.onResetProperties()
- qsTile?.longClick(expandable)
+ setState(State.IDLE)
}
+ qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
- setState(State.IDLE)
callback?.onEffectFinishedReversing()
+ setState(getStateForClick())
qsTile?.click(expandable)
}
State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE)
@@ -160,14 +163,37 @@ constructor(
}
fun onTileClick(): Boolean {
- if (state == State.TIMEOUT_WAIT) {
- setState(State.IDLE)
- qsTile?.let {
- it.click(expandable)
- return true
- }
+ val isStateClickable = state == State.TIMEOUT_WAIT || state == State.IDLE
+
+ // Ignore View-generated clicks on invalid states or if the bouncer is showing
+ if (keyguardStateController.isPrimaryBouncerShowing || !isStateClickable) return false
+
+ setState(getStateForClick())
+ qsTile?.click(expandable)
+ return true
+ }
+
+ /**
+ * Get the appropriate state for a click action.
+ *
+ * In some occasions, the click action will not result in a subsequent action that resets the
+ * state upon completion (e.g., a launch transition animation). In these cases, the state needs
+ * to be reset before the click is dispatched.
+ */
+ @VisibleForTesting
+ fun getStateForClick(): State {
+ val isTileUnavailable = qsTile?.state?.state == Tile.STATE_UNAVAILABLE
+ val isFalseTapWhileLocked =
+ !keyguardStateController.isUnlocked &&
+ falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ val handlesLongClick = qsTile?.state?.handlesLongClick == true
+ return if (isTileUnavailable || isFalseTapWhileLocked || !handlesLongClick) {
+ // The click event will not perform an action that resets the state. Therefore, this is
+ // the last opportunity to reset the state back to IDLE.
+ State.IDLE
+ } else {
+ State.CLICKED
}
- return false
}
/**
@@ -194,6 +220,8 @@ constructor(
return true
}
+ fun resetState() = setState(State.IDLE)
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
@@ -202,6 +230,8 @@ constructor(
RUNNING_BACKWARDS_FROM_UP,
/* The effect was interrupted by an ACTION_CANCEL and is now running backwards */
RUNNING_BACKWARDS_FROM_CANCEL,
+ CLICKED, /* The effect has ended with a click */
+ LONG_CLICKED, /* The effect has ended with a long-click */
}
/** Callbacks to notify view and animator actions */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 44c846b5c631..787fd1ab7170 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -386,6 +386,7 @@ constructor(
// The launch animation of a long-press effect did not reset the long-press effect so
// we must do it here
resetLongPressEffectProperties()
+ longPressEffect.resetState()
}
val actualHeight =
if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
@@ -771,11 +772,14 @@ constructor(
lastIconTint = icon.getColor(state)
// Long-press effects
+ longPressEffect?.qsTile?.state?.handlesLongClick = state.handlesLongClick
if (
state.handlesLongClick &&
longPressEffect?.initializeEffect(longPressEffectDuration) == true
) {
showRippleEffect = false
+ longPressEffect.qsTile?.state?.state = lastState // Store the tile's state
+ longPressEffect.resetState()
initializeLongPressProperties(measuredHeight, measuredWidth)
} else {
// Long-press effects might have been enabled before but the new state does not
@@ -906,6 +910,7 @@ constructor(
}
override fun onActivityLaunchAnimationEnd() {
+ longPressEffect?.resetState()
if (longPressEffect != null && !haveLongPressPropertiesBeenReset) {
resetLongPressEffectProperties()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index eff99e047f45..28355e1c3efa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,9 +16,10 @@
package com.android.systemui.haptics.qs
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController, falsingManager) }