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

Commit 156afbb6 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Playing device entry haptics from the DevieEntryIcon.

Whenever entering the device via the DeviceEntryIcon, we also play the
device entry haptics. This keeps the CUJ consistent with all the other
forms of haptic feedback when entering the device. We also keep the
previous behavior consistent when pressing the icon opens the bouncer
instead of entering the device. In such a case, the long-press MSDL
feedback token plays.

Test: DeviceEntryHapticsInteractorTest
Flag: com.android.systemui.msdl_feedback
Bug: 403255619
Change-Id: I1d6f985c42a17b0b53e3a26ae673c4ba9f3919a4
parent 4df49144
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.systemui.log.dagger.LongPressTouchLog
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -66,6 +67,7 @@ constructor(
    private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
    private val falsingManager: Lazy<FalsingManager>,
    private val vibratorHelper: Lazy<VibratorHelper>,
    private val msdlPlayer: Lazy<MSDLPlayer>,
    @LongPressTouchLog private val logBuffer: LogBuffer,
) {
    @Composable
@@ -90,6 +92,7 @@ constructor(
                            deviceEntryBackgroundViewModel.get(),
                            falsingManager.get(),
                            vibratorHelper.get(),
                            msdlPlayer.get(),
                            overrideColor,
                        )
                    }
+14 −0
Original line number Diff line number Diff line
@@ -311,6 +311,20 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
            assertThat(playSuccessHaptic).isNull()
        }

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

            kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
            runCurrent()
            kosmos.deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()

            assertThat(playSuccessHaptic).isNotNull()
        }

    // Mock dependencies for DeviceEntrySourceInteractor#deviceEntryFromBiometricSource
    private fun configureDeviceEntryFromBiometricSource(
        isFpUnlock: Boolean = false,
+12 −7
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@
package com.android.systemui.deviceentry.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -31,13 +33,19 @@ class AuthRippleInteractor
constructor(
    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
    keyguardInteractor: KeyguardInteractor,
) {
    private val successfulEntryFromDeviceEntryIcon: Flow<Unit> =
        deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon
            .map { keyguardInteractor.isKeyguardDismissible.value }
            .filter { it } // only emit events if the keyguard is dismissible
            // map to Unit
            .map {}

    private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
        deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
            if (isUdfpsSupported) {
                deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
                    BiometricUnlockSource.FINGERPRINT_SENSOR
                }
                successfulEntryFromDeviceEntryIcon.map { BiometricUnlockSource.FINGERPRINT_SENSOR }
            } else {
                emptyFlow()
            }
@@ -46,8 +54,5 @@ constructor(
    private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
    val showUnlockRipple: Flow<BiometricUnlockSource> =
        merge(
            showUnlockRippleFromDeviceEntryIcon,
            showUnlockRippleFromBiometricUnlock,
        )
        merge(showUnlockRippleFromDeviceEntryIcon, showUnlockRippleFromBiometricUnlock)
}
+29 −7
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@
package com.android.systemui.deviceentry.domain.interactor

import com.android.keyguard.logging.BiometricUnlockLogger
import com.android.systemui.Flags
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
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.KeyguardInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -54,6 +56,7 @@ constructor(
    keyEventInteractor: KeyEventInteractor,
    private val logger: BiometricUnlockLogger,
    powerInteractor: PowerInteractor,
    keyguardInteractor: KeyguardInteractor,
    private val systemClock: SystemClock,
    dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
@@ -80,12 +83,7 @@ constructor(
                emit(recentPowerButtonPressThresholdMs * -1L - 1L)
            }

    /**
     * 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> =
    private val playSuccessHapticOnDeviceEntryFromBiometricSource: Flow<Unit> =
        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
            .sample(
                combine(
@@ -108,7 +106,31 @@ constructor(
            }
            // map to Unit
            .map {}

    private val playSuccessHapticOnDeviceEntryFromDeviceEntryIcon: Flow<Unit> =
        deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon
            .map { keyguardInteractor.isKeyguardDismissible.value }
            .filter { it } // only play if the keyguard is dismissible
            // map to Unit
            .map {}

    /**
     * Indicates when success haptics should play when the device is entered. When entering via a
     * biometric sources, 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> =
        if (Flags.msdlFeedback()) {
            merge(
                    playSuccessHapticOnDeviceEntryFromBiometricSource,
                    playSuccessHapticOnDeviceEntryFromDeviceEntryIcon,
                )
                .dumpWhileCollecting("playSuccessHaptic")
        } else {
            playSuccessHapticOnDeviceEntryFromBiometricSource.dumpWhileCollecting(
                "playSuccessHaptic"
            )
        }

    private val playErrorHapticForBiometricFailure: Flow<Unit> =
        merge(
+4 −7
Original line number Diff line number Diff line
@@ -254,15 +254,12 @@ constructor(
            }
            .dumpWhileCollecting("deviceEntryFromBiometricSource")

    private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
    val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
        attemptEnterDeviceFromDeviceEntryIcon
            .sample(keyguardInteractor.isKeyguardDismissible)
            .filter { it } // only send events if the keyguard is dismissible
            .map {} // map to Unit
    private val _attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> =
        MutableSharedFlow()
    val attemptEnterDeviceFromDeviceEntryIcon = _attemptEnterDeviceFromDeviceEntryIcon

    suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
        attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
        _attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
    }

    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode {
Loading