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

Commit 4fea664d authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Reverting to a previous state of face auth haptics.

In the previous state, we:

1. Play success and failure haptics for all cases of fingerprint
   authentication.
2. Play success haptics when unlocking with the face only if the "skip
   lockscreen" setting is on.
3. Play failure haptics when failing to authenticate with the face only
   if face is the single biometric authentication method in the device.
   In other words, failing to unlock via face won't play haptics if a
   fingerprint is registered. If face fails, the user still has to go
   through the fingerprint CUJs to either successfully unlock or fail
   again. Since these are the last stages that resolve the
   authentication intent, these are the ones that play haptics

Test: unit tests updated
Test: manual. Verified cases 1, 2 and 3 above by feeling the correct
  success and failure haptic patterns in those conditions.
Flag: com.android.systemui.msdl_feedback
Bug: 391789101
Change-Id: I542f578c9707988e7a80966f29414ae5280ef00c
parent 14cb9b41
Loading
Loading
Loading
Loading
+16 −14
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.phone.dozeScrimController
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@@ -105,7 +106,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
    @Test
    fun nonPowerButtonFPS_vibrateSuccess() =
        testScope.runTest {
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
            runCurrent()
            enterDeviceFromFingerprintUnlockLegacy()
@@ -116,7 +117,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
    @Test
    fun powerButtonFPS_vibrateSuccess() =
        testScope.runTest {
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)

@@ -133,7 +134,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
    @Test
    fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
        testScope.runTest {
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN

@@ -150,7 +151,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
    @Test
    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
        testScope.runTest {
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)

@@ -174,14 +175,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
        }

    @Test
    fun nonPowerButtonFPS_coExFaceFailure_vibrateError() =
    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
        testScope.runTest {
            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
            enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
            enrollFace()
            runCurrent()
            faceFailure()
            assertThat(playErrorHaptic).isNotNull()
            assertThat(playErrorHaptic).isNull()
        }

    @Test
@@ -211,7 +212,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
        testScope.runTest {
            kosmos.configureKeyguardBypass(isBypassAvailable = false)
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
            runCurrent()
            configureDeviceEntryFromBiometricSource(isFpUnlock = true)
@@ -225,7 +226,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
        testScope.runTest {
            kosmos.configureKeyguardBypass(isBypassAvailable = false)
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)

@@ -246,18 +247,19 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
            enrollFace()
            kosmos.configureKeyguardBypass(isBypassAvailable = true)
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            configureDeviceEntryFromBiometricSource(isFaceUnlock = true)
            verifyDeviceEntryFromFaceAuth()
            assertThat(playSuccessHaptic).isNotNull()
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableSceneContainer
    @Test
    fun playSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() =
    fun skipSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() =
        testScope.runTest {
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)

            enrollFace()
            kosmos.configureKeyguardBypass(isBypassAvailable = false)
@@ -265,7 +267,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
            configureDeviceEntryFromBiometricSource(isFaceUnlock = true, bypassEnabled = false)
            kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true

            assertThat(playSuccessHaptic).isNotNull()
            assertThat(playSuccessHaptic).isNull()
        }

    @EnableSceneContainer
@@ -274,7 +276,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
        testScope.runTest {
            kosmos.configureKeyguardBypass(isBypassAvailable = false)
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            // power button is currently DOWN
            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
@@ -295,7 +297,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
        testScope.runTest {
            kosmos.configureKeyguardBypass(isBypassAvailable = false)
            underTest = kosmos.deviceEntryHapticsInteractor
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
            val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
            enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)

+15 −17
Original line number Diff line number Diff line
@@ -863,7 +863,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasUdfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -885,7 +885,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasUdfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -907,7 +907,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -930,7 +930,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1033,7 +1033,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1056,7 +1056,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1079,7 +1079,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1102,7 +1102,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
            whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
@@ -1160,7 +1160,7 @@ class SceneContainerStartableTest : SysuiTestCase() {

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playsFaceErrorHaptics_nonSfps_coEx() =
    fun skipsFaceErrorHaptics_nonSfps_coEx() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1172,15 +1172,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
            underTest.start()
            updateFaceAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper).vibrateAuthError(anyString())
            assertThat(playErrorHaptic).isNull()
            verify(vibratorHelper, never()).vibrateAuthError(anyString())
            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playsMSDLFaceErrorHaptics_nonSfps_coEx() =
    fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1192,10 +1191,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
            underTest.start()
            updateFaceAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
            assertThat(playErrorHaptic).isNull()
            assertThat(msdlPlayer.latestTokenPlayed).isNull()
            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
        }

    @Test
+0 −6
Original line number Diff line number Diff line
@@ -68,10 +68,4 @@ constructor(
                emptyFlow()
            }
        }

    /** Triggered if a face failure occurs regardless of the mode. */
    val faceFailure: Flow<FailedFaceAuthenticationStatus> =
        deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
            FailedFaceAuthenticationStatus
        >()
}
+9 −19
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -49,8 +48,6 @@ class DeviceEntryHapticsInteractor
constructor(
    biometricSettingsRepository: BiometricSettingsRepository,
    deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
    deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
    keyguardBypassInteractor: KeyguardBypassInteractor,
    deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
    fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -83,7 +80,12 @@ constructor(
                emit(recentPowerButtonPressThresholdMs * -1L - 1L)
            }

    private val playHapticsOnDeviceEntry: Flow<Boolean> =
    /**
     * Indicates when success haptics should play when the device is entered. This always occurs on
     * successful fingerprint authentications. It also occurs on successful face authentication but
     * only if the lockscreen is bypassed.
     */
    val playSuccessHapticOnDeviceEntry: Flow<Unit> =
        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
            .sample(
                combine(
@@ -93,29 +95,17 @@ constructor(
                    ::Triple,
                )
            )
            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
            .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
                val sideFpsAllowsHaptic =
                    !powerButtonDown &&
                        systemClock.uptimeMillis() - lastPowerButtonWakeup >
                            recentPowerButtonPressThresholdMs
                val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
                if (!allowHaptic) {
                    logger.d(
                        "Skip success entry haptic from power button. Recent power button press or button is down."
                    )
                    logger.d("Skip success haptic. Recent power button press or button is down.")
                }
                allowHaptic
            }

    private val playHapticsOnFaceAuthSuccessAndBypassDisabled: Flow<Boolean> =
        deviceEntryFaceAuthInteractor.isAuthenticated
            .filter { it }
            .sample(keyguardBypassInteractor.isBypassAvailable)
            .map { !it }

    val playSuccessHaptic: Flow<Unit> =
        merge(playHapticsOnDeviceEntry, playHapticsOnFaceAuthSuccessAndBypassDisabled)
            .filter { it }
            // map to Unit
            .map {}
            .dumpWhileCollecting("playSuccessHaptic")
@@ -123,7 +113,7 @@ constructor(
    private val playErrorHapticForBiometricFailure: Flow<Unit> =
        merge(
                deviceEntryFingerprintAuthInteractor.fingerprintFailure,
                deviceEntryBiometricAuthInteractor.faceFailure,
                deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
            )
            // map to Unit
            .map {}
+1 −1
Original line number Diff line number Diff line
@@ -333,7 +333,7 @@ object KeyguardRootViewBinder {

                    if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                        launch {
                            deviceEntryHapticsInteractor.playSuccessHaptic.collect {
                            deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry.collect {
                                if (msdlFeedback()) {
                                    msdlPlayer?.playToken(
                                        MSDLToken.UNLOCK,
Loading