Loading packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +296 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 { Loading Loading @@ -1841,4 +2049,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() ) } } } packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +41 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading Loading @@ -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() Loading @@ -148,6 +153,7 @@ constructor( respondToFalsingDetections() hydrateInteractionState() handleBouncerOverscroll() handleDeviceEntryHapticsWhileDeviceLocked() hydrateWindowController() hydrateBackStack() resetShadeSessions() Loading Loading @@ -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 { Loading Loading @@ -808,4 +845,8 @@ constructor( .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() } } } companion object { private const val TAG = "SceneContainerStartable" } } packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +6 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt +3 −1 Original line number Diff line number Diff line Loading @@ -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
packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +296 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 { Loading Loading @@ -1841,4 +2049,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() ) } } }
packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +41 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading Loading @@ -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() Loading @@ -148,6 +153,7 @@ constructor( respondToFalsingDetections() hydrateInteractionState() handleBouncerOverscroll() handleDeviceEntryHapticsWhileDeviceLocked() hydrateWindowController() hydrateBackStack() resetShadeSessions() Loading Loading @@ -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 { Loading Loading @@ -808,4 +845,8 @@ constructor( .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() } } } companion object { private const val TAG = "SceneContainerStartable" } }
packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +6 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading
packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt +3 −1 Original line number Diff line number Diff line Loading @@ -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() }