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

Commit bed96eb1 authored by Grace Cheng's avatar Grace Cheng
Browse files

[flexiglass] Implement fp success and error haptics

Implements fingerprint success and error haptics across all biometric
auth-related scenes: lockscreen, bouncer, quick settings, and shade, and
adds tests for the new haptics implementation.

Haptics was previously handled in KeyguardRootViewBinder, which was
bound in KeyguardViewConfigurator and played the haptics with
vibratorHelper.performHapticFeedback(view = KeyguardRootView,
feedbackConstant = HapticFeedbackConstants.BIOMETRIC_CONFIRM /
HapticFeedbackConstants.BIOMETRIC_REJECT).

The new flexiglass implementation doesn’t use KeyguardViewConfigurator,
and since haptics is applicable across multiple scenes, we migrate it to
SceneContainerStartable, which runs coroutine jobs that outlive the
scene transitions. Since it no longer has a view reference, we use
vibratorHelper.vibrateAuthSuccess and vibrationHelper.vibrateAuthError
to play the haptics.

vibrateAuthSuccess uses BIOMETRIC_SUCCESS_VIBRATION_EFFECT, which maps
to VibrationEffect.EFFECT_CLICK, the same effect
HapticFeedbackVibrationProvider uses for
HapticFeedbackConstants.BIOMETRIC_CONFIRM. vibrateAuthError uses
BIOMETRIC_ERROR_VIBRATION_EFFECT, which maps to
VibrationEffect.EFFECT_DOUBLE_CLICK, the same effect
HapticFeedbackVibrationProvider uses for
HapticFeedbackConstants.BIOMETRIC_REJECT.

Also updates VibrationHelperKosmos, which was using a FakeVibratorHelper
type in previous use cases. We need to mock VibrationHelper in test to
ensure the expected haptics methods are called, so we create a new
Kosmos.fakeVibratorHelper that is the default value for
Kosmos.vibratorHelper for previous use cases, and override it to a
VibratorHelper mock in this test.

Flag: com.android.systemui.scene_container
Fixes: 352764632
Fixes: 352766437
Fixes: 352765379
Fixes: 352762251
Test: manually verified success and error haptics on SFPS, UDFPS, RFPS devices on lock screen, primary bouncer, alternate bouncer
Test: atest SceneContainerStartableTest
Test: atest QSLongPressEffectTest
Test: atest SliderHapticFeedbackProviderTest
Change-Id: Ic04ae3db137f6413515cb72da22a8d11db6a9a7e
parent 5f1c1da0
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.qs.qsTileFactory
@@ -50,7 +50,7 @@ class QSLongPressEffectTest : SysuiTestCase() {

    @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
    private val kosmos = testKosmos()
    private val vibratorHelper = kosmos.vibratorHelper
    private val vibratorHelper = kosmos.fakeVibratorHelper
    private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
    @Mock private lateinit var callback: QSLongPressEffect.Callback
    @Mock private lateinit var controller: ActivityTransitionAnimator.Controller
+296 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
package com.android.systemui.scene.domain.startable

import android.app.StatusBarManager
import android.hardware.face.FaceManager
import android.os.PowerManager
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,6 +31,9 @@ import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
@@ -39,8 +43,14 @@ import com.android.systemui.classifier.falsingManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -53,11 +63,15 @@ import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -69,6 +83,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
@@ -85,6 +100,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -92,6 +108,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -105,12 +123,14 @@ class SceneContainerStartableTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val deviceEntryHapticsInteractor by lazy { kosmos.deviceEntryHapticsInteractor }
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
    private val sysUiState = kosmos.sysUiState
    private val falsingCollector = mock<FalsingCollector>().also { kosmos.falsingCollector = it }
    private val vibratorHelper = mock<VibratorHelper>().also { kosmos.vibratorHelper = it }
    private val fakeSceneDataSource = kosmos.fakeSceneDataSource
    private val windowController = kosmos.notificationShadeWindowController
    private val centralSurfaces = kosmos.centralSurfaces
@@ -633,6 +653,194 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @Test
    fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)

            setupBiometricAuth(hasUdfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            unlockWithFingerprintAuth()

            assertThat(playSuccessHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper)
                .vibrateAuthSuccess(
                    "SceneContainerStartable, $currentSceneKey device-entry::success"
                )
            verify(vibratorHelper, never()).vibrateAuthError(anyString())

            updateFingerprintAuthStatus(isSuccess = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @Test
    fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            allowHapticsOnSfps()
            unlockWithFingerprintAuth()

            assertThat(playSuccessHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper)
                .vibrateAuthSuccess(
                    "SceneContainerStartable, $currentSceneKey device-entry::success"
                )
            verify(vibratorHelper, never()).vibrateAuthError(anyString())

            updateFingerprintAuthStatus(isSuccess = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @Test
    fun playErrorHaptics_onFailedLockscreenAuth_udfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)

            setupBiometricAuth(hasUdfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            updateFingerprintAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper)
                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
        }

    @Test
    fun playErrorHaptics_onFailedLockscreenAuth_sfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            updateFingerprintAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNotNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper)
                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
        }

    @Test
    fun skipsSuccessHaptics_whenPowerButtonDown_sfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            allowHapticsOnSfps(isPowerButtonDown = true)
            unlockWithFingerprintAuth()

            assertThat(playSuccessHaptic).isNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper, never())
                .vibrateAuthSuccess(
                    "SceneContainerStartable, $currentSceneKey device-entry::success"
                )
            verify(vibratorHelper, never()).vibrateAuthError(anyString())

            updateFingerprintAuthStatus(isSuccess = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @Test
    fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playSuccessHaptic by
                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            allowHapticsOnSfps(lastPowerPress = 50)
            unlockWithFingerprintAuth()

            assertThat(playSuccessHaptic).isNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper, never())
                .vibrateAuthSuccess(
                    "SceneContainerStartable, $currentSceneKey device-entry::success"
                )
            verify(vibratorHelper, never()).vibrateAuthError(anyString())

            updateFingerprintAuthStatus(isSuccess = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @Test
    fun skipsErrorHaptics_whenPowerButtonDown_sfps() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)

            setupBiometricAuth(hasSfps = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
            updateFingerprintAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper, never())
                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
        }

    @Test
    fun skipsFaceErrorHaptics_nonSfps_coEx() =
        testScope.runTest {
            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)

            setupBiometricAuth(hasUdfps = true, hasFace = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()

            underTest.start()
            updateFaceAuthStatus(isSuccess = false)

            assertThat(playErrorHaptic).isNull()
            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
            verify(vibratorHelper, never())
                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
        }

    @Test
    fun hydrateSystemUiState() =
        testScope.runTest {
@@ -1876,4 +2084,92 @@ class SceneContainerStartableTest : SysuiTestCase() {
        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
            this.isPinned.value = isPinned
        }

    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
        kosmos.fingerprintPropertyRepository.setProperties(
            sensorId = 0,
            strength = SensorStrength.STRONG,
            sensorType = fingerprintSensorType,
            sensorLocations = mapOf(),
        )
        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
    }

    private fun setFaceEnrolled() {
        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
    }

    private fun TestScope.allowHapticsOnSfps(
        isPowerButtonDown: Boolean = false,
        lastPowerPress: Long = 10000
    ) {
        kosmos.fakeKeyEventRepository.setPowerButtonDown(isPowerButtonDown)

        kosmos.powerRepository.updateWakefulness(
            WakefulnessState.AWAKE,
            WakeSleepReason.POWER_BUTTON,
            WakeSleepReason.POWER_BUTTON,
            powerButtonLaunchGestureTriggered = false,
        )

        advanceTimeBy(lastPowerPress)
        runCurrent()
    }

    private fun unlockWithFingerprintAuth() {
        kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
            BiometricUnlockSource.FINGERPRINT_SENSOR
        )
        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.UNLOCK_COLLAPSING)
    }

    private fun TestScope.setupBiometricAuth(
        hasSfps: Boolean = false,
        hasUdfps: Boolean = false,
        hasFace: Boolean = false
    ) {
        if (hasSfps) {
            setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
        }

        if (hasUdfps) {
            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
        }

        if (hasFace) {
            setFaceEnrolled()
        }

        prepareState(
            authenticationMethod = AuthenticationMethodModel.Pin,
            isDeviceUnlocked = false,
            initialSceneKey = Scenes.Lockscreen,
        )
    }

    private fun updateFingerprintAuthStatus(isSuccess: Boolean) {
        if (isSuccess) {
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
        } else {
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                FailFingerprintAuthenticationStatus
            )
        }
    }

    private fun updateFaceAuthStatus(isSuccess: Boolean) {
        if (isSuccess) {
            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
                SuccessFaceAuthenticationStatus(
                    successResult = Mockito.mock(FaceManager.AuthenticationResult::class.java)
                )
            )
        } else {
            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
                FailedFaceAuthenticationStatus()
            )
        }
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
@@ -62,6 +63,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -78,6 +80,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
@@ -107,6 +110,7 @@ constructor(
    @Application private val applicationScope: CoroutineScope,
    private val sceneInteractor: SceneInteractor,
    private val deviceEntryInteractor: DeviceEntryInteractor,
    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
    private val bouncerInteractor: BouncerInteractor,
    private val keyguardInteractor: KeyguardInteractor,
@@ -134,6 +138,7 @@ constructor(
    private val dismissCallbackRegistry: DismissCallbackRegistry,
    private val statusBarStateController: SysuiStatusBarStateController,
    private val alternateBouncerInteractor: AlternateBouncerInteractor,
    private val vibratorHelper: VibratorHelper,
) : CoreStartable {
    private val centralSurfaces: CentralSurfaces?
        get() = centralSurfacesOptLazy.get().getOrNull()
@@ -148,6 +153,7 @@ constructor(
            respondToFalsingDetections()
            hydrateInteractionState()
            handleBouncerOverscroll()
            handleDeviceEntryHapticsWhileDeviceLocked()
            hydrateWindowController()
            hydrateBackStack()
            resetShadeSessions()
@@ -525,6 +531,37 @@ constructor(
        }
    }

    private fun handleDeviceEntryHapticsWhileDeviceLocked() {
        applicationScope.launch {
            deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
                // Only check for haptics signals before device is entered
                if (!isDeviceEntered) {
                    coroutineScope {
                        launch {
                            deviceEntryHapticsInteractor.playSuccessHaptic
                                .sample(sceneInteractor.currentScene)
                                .collect { currentScene ->
                                    vibratorHelper.vibrateAuthSuccess(
                                        "$TAG, $currentScene device-entry::success"
                                    )
                                }
                        }

                        launch {
                            deviceEntryHapticsInteractor.playErrorHaptic
                                .sample(sceneInteractor.currentScene)
                                .collect { currentScene ->
                                    vibratorHelper.vibrateAuthError(
                                        "$TAG, $currentScene device-entry::error"
                                    )
                                }
                        }
                    }
                }
            }
        }
    }

    /** Keeps [SysUiState] up-to-date */
    private fun hydrateSystemUiState() {
        applicationScope.launch {
@@ -817,4 +854,8 @@ constructor(
                .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() }
        }
    }

    companion object {
        private const val TAG = "SceneContainerStartable"
    }
}
+6 −5
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import android.view.VelocityTracker
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.fakeSystemClock
@@ -47,6 +47,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    private val lowTickDuration = 12 // Mocked duration of a low tick
    private val dragTextureThresholdMillis =
        lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
    private val vibratorHelper = kosmos.fakeVibratorHelper
    private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider

    @Before
@@ -56,11 +57,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
            .thenReturn(config.maxVelocityToScale)

        kosmos.vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
        vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
            lowTickDuration
        sliderHapticFeedbackProvider =
            SliderHapticFeedbackProvider(
                kosmos.vibratorHelper,
                vibratorHelper,
                velocityTracker,
                config,
                kosmos.fakeSystemClock,
+3 −1
Original line number Diff line number Diff line
@@ -17,5 +17,7 @@
package com.android.systemui.haptics

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.VibratorHelper

var Kosmos.vibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
var Kosmos.vibratorHelper: VibratorHelper by Kosmos.Fixture { fakeVibratorHelper }
val Kosmos.fakeVibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
Loading