diff options
| author | 2024-10-30 19:37:37 +0000 | |
|---|---|---|
| committer | 2024-10-30 19:37:37 +0000 | |
| commit | 6b37ca81979168bf46421ba437ac4a5dab2cf1c1 (patch) | |
| tree | c83ef3f5c786cf1b98f663864b7852df127ef2ad | |
| parent | c2ad1ac2001dc62a7da7fc811341bbc52a97da3a (diff) | |
| parent | 2410a73f7696390dde8c685890beab5bf27f909a (diff) | |
Merge "[flexiglass] Migrate keyguard bypass, biometric unlock state, and related haptics logic" into main
44 files changed, 2184 insertions, 403 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index 2b7e7adbe022..20d66155e5ca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -16,34 +16,57 @@ package com.android.systemui.deviceentry.domain.interactor +import android.hardware.face.FaceManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.keyguard.keyguardUpdateMonitor 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.authController 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.keyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.configureKeyguardBypass import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository 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.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.powerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +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 import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -51,24 +74,52 @@ import org.junit.runner.RunWith class DeviceEntryHapticsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest = kosmos.deviceEntryHapticsInteractor + private lateinit var underTest: DeviceEntryHapticsInteractor + + @Before + fun setup() { + if (SceneContainerFlag.isEnabled) { + whenever(kosmos.authController.isUdfpsFingerDown).thenReturn(false) + whenever(kosmos.dozeScrimController.isPulsing).thenReturn(false) + whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + whenever(kosmos.screenOffAnimationController.isKeyguardShowDelayed()).thenReturn(false) + + // Dependencies for DeviceEntrySourceInteractor#biometricUnlockStateOnKeyguardDismissed + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + + // Mock authenticationMethodIsSecure true + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + + kosmos.keyguardBouncerRepository.setAlternateVisible(false) + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + } else { + underTest = kosmos.deviceEntryHapticsInteractor + } + } + + @DisableSceneContainer @Test fun nonPowerButtonFPS_vibrateSuccess() = testScope.runTest { val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() - enterDeviceFromBiometricUnlock() + enterDeviceFromFingerprintUnlockLegacy() assertThat(playSuccessHaptic).isNotNull() } + @DisableSceneContainer @Test fun powerButtonFPS_vibrateSuccess() = testScope.runTest { val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) // It's been 10 seconds since the last power button wakeup @@ -76,16 +127,16 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { advanceTimeBy(10000) runCurrent() - enterDeviceFromBiometricUnlock() + enterDeviceFromFingerprintUnlockLegacy() assertThat(playSuccessHaptic).isNotNull() } + @DisableSceneContainer @Test fun powerButtonFPS_powerDown_doNotVibrateSuccess() = testScope.runTest { val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN // It's been 10 seconds since the last power button wakeup @@ -93,16 +144,16 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { advanceTimeBy(10000) runCurrent() - enterDeviceFromBiometricUnlock() + enterDeviceFromFingerprintUnlockLegacy() assertThat(playSuccessHaptic).isNull() } + @DisableSceneContainer @Test fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = testScope.runTest { val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) // It's only been 50ms since the last power button wakeup @@ -110,7 +161,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { advanceTimeBy(50) runCurrent() - enterDeviceFromBiometricUnlock() + enterDeviceFromFingerprintUnlockLegacy() assertThat(playSuccessHaptic).isNull() } @@ -118,7 +169,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { fun nonPowerButtonFPS_vibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() fingerprintFailure() assertThat(playErrorHaptic).isNotNull() @@ -128,8 +179,8 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) - coExEnrolledAndEnabled() + enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) + enrollFace() runCurrent() faceFailure() assertThat(playErrorHaptic).isNull() @@ -139,8 +190,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { fun powerButtonFPS_vibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) runCurrent() fingerprintFailure() assertThat(playErrorHaptic).isNotNull() @@ -150,15 +200,143 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { fun powerButtonFPS_powerDown_doNotVibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(true) runCurrent() fingerprintFailure() assertThat(playErrorHaptic).isNull() } - private suspend fun enterDeviceFromBiometricUnlock() { + @EnableSceneContainer + @Test + fun playSuccessHaptic_onDeviceEntryFromUdfps_sceneContainer() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntryHapticsInteractor + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) + runCurrent() + configureDeviceEntryFromBiometricSource(isFpUnlock = true) + verifyDeviceEntryFromFingerprintAuth() + assertThat(playSuccessHaptic).isNotNull() + } + + @EnableSceneContainer + @Test + fun playSuccessHaptic_onDeviceEntryFromSfps_sceneContainer() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntryHapticsInteractor + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) + kosmos.fakeKeyEventRepository.setPowerButtonDown(false) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + advanceTimeBy(10000) + runCurrent() + + configureDeviceEntryFromBiometricSource(isFpUnlock = true) + verifyDeviceEntryFromFingerprintAuth() + assertThat(playSuccessHaptic).isNotNull() + } + + @EnableSceneContainer + @Test + fun playSuccessHaptic_onDeviceEntryFromFaceAuth_sceneContainer() = + testScope.runTest { + enrollFace() + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntryHapticsInteractor + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + configureDeviceEntryFromBiometricSource(isFaceUnlock = true) + verifyDeviceEntryFromFaceAuth() + assertThat(playSuccessHaptic).isNotNull() + } + + @EnableSceneContainer + @Test + fun skipSuccessHaptic_onDeviceEntryFromSfps_whenPowerDown_sceneContainer() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntryHapticsInteractor + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) + // power button is currently DOWN + kosmos.fakeKeyEventRepository.setPowerButtonDown(true) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + advanceTimeBy(10000) + runCurrent() + + configureDeviceEntryFromBiometricSource(isFpUnlock = true) + verifyDeviceEntryFromFingerprintAuth() + assertThat(playSuccessHaptic).isNull() + } + + @EnableSceneContainer + @Test + fun skipSuccessHaptic_onDeviceEntryFromSfps_whenPowerButtonRecentlyPressed_sceneContainer() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntryHapticsInteractor + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + enrollFingerprint(FingerprintSensorType.POWER_BUTTON) + kosmos.fakeKeyEventRepository.setPowerButtonDown(false) + + // It's only been 50ms since the last power button wakeup + setAwakeFromPowerButton() + advanceTimeBy(50) + runCurrent() + + configureDeviceEntryFromBiometricSource(isFpUnlock = true) + verifyDeviceEntryFromFingerprintAuth() + assertThat(playSuccessHaptic).isNull() + } + + // Mock dependencies for DeviceEntrySourceInteractor#deviceEntryFromBiometricSource + private fun configureDeviceEntryFromBiometricSource( + isFpUnlock: Boolean = false, + isFaceUnlock: Boolean = false, + ) { + // Mock DeviceEntrySourceInteractor#deviceEntryBiometricAuthSuccessState + if (isFpUnlock) { + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + } + if (isFaceUnlock) { + kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( + SuccessFaceAuthenticationStatus( + FaceManager.AuthenticationResult(null, null, 0, true) + ) + ) + + // Mock DeviceEntrySourceInteractor#faceWakeAndUnlockMode = MODE_UNLOCK_COLLAPSING + kosmos.sceneInteractor.setTransitionState( + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + ) + } + underTest = kosmos.deviceEntryHapticsInteractor + } + + private fun TestScope.verifyDeviceEntryFromFingerprintAuth() { + val deviceEntryFromBiometricSource by + collectLastValue(kosmos.deviceEntrySourceInteractor.deviceEntryFromBiometricSource) + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + private fun TestScope.verifyDeviceEntryFromFaceAuth() { + val deviceEntryFromBiometricSource by + collectLastValue(kosmos.deviceEntrySourceInteractor.deviceEntryFromBiometricSource) + assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + private fun enterDeviceFromFingerprintUnlockLegacy() { kosmos.fakeKeyguardRepository.setBiometricUnlockSource( BiometricUnlockSource.FINGERPRINT_SENSOR ) @@ -177,21 +355,22 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { ) } - private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { - kosmos.fingerprintPropertyRepository.setProperties( - sensorId = 0, - strength = SensorStrength.STRONG, - sensorType = fingerprintSensorType, - sensorLocations = mapOf(), - ) - } - - private fun setPowerButtonFingerprintProperty() { - setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON) + private fun enrollFingerprint(fingerprintSensorType: FingerprintSensorType?) { + if (fingerprintSensorType == null) { + kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + } else { + kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + kosmos.fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = fingerprintSensorType, + sensorLocations = mapOf(), + ) + } } - private fun setFingerprintEnrolled() { - kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + private fun enrollFace() { + kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) } private fun setAwakeFromPowerButton() { @@ -202,9 +381,4 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { powerButtonLaunchGestureTriggered = false, ) } - - private fun coExEnrolledAndEnabled() { - setFingerprintEnrolled() - kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt index 2e4c97bc80d0..b3c891dc9ac6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt @@ -16,21 +16,51 @@ package com.android.systemui.deviceentry.domain.interactor +import android.hardware.face.FaceManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.keyguard.keyguardUpdateMonitor 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.authController +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus +import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.configureKeyguardBypass +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.keyguardBypassRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.data.repository.verifyCallback import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.phone.dozeScrimController +import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.statusbar.policy.devicePostureController 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.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -38,45 +68,328 @@ import org.junit.runner.RunWith class DeviceEntrySourceInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val keyguardRepository = kosmos.fakeKeyguardRepository - private val underTest = kosmos.deviceEntrySourceInteractor + private lateinit var underTest: DeviceEntrySourceInteractor + @Before + fun setup() { + if (SceneContainerFlag.isEnabled) { + whenever(kosmos.authController.isUdfpsFingerDown).thenReturn(false) + whenever(kosmos.dozeScrimController.isPulsing).thenReturn(false) + whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) + whenever(kosmos.screenOffAnimationController.isKeyguardShowDelayed()).thenReturn(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + } else { + underTest = kosmos.deviceEntrySourceInteractor + } + } + + @DisableSceneContainer @Test fun deviceEntryFromFaceUnlock() = testScope.runTest { val deviceEntryFromBiometricAuthentication by collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockState( + + kosmos.fakeKeyguardRepository.setBiometricUnlockState( BiometricUnlockMode.WAKE_AND_UNLOCK, BiometricUnlockSource.FACE_SENSOR, ) runCurrent() + assertThat(deviceEntryFromBiometricAuthentication) .isEqualTo(BiometricUnlockSource.FACE_SENSOR) } + @DisableSceneContainer @Test - fun deviceEntryFromFingerprintUnlock() = runTest { - val deviceEntryFromBiometricAuthentication by - collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockState( - BiometricUnlockMode.WAKE_AND_UNLOCK, - BiometricUnlockSource.FINGERPRINT_SENSOR, - ) - runCurrent() - assertThat(deviceEntryFromBiometricAuthentication) - .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) - } + fun deviceEntryFromFingerprintUnlock() = + testScope.runTest { + val deviceEntryFromBiometricAuthentication by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + kosmos.fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricAuthentication) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @DisableSceneContainer + @Test + fun noDeviceEntry() = + testScope.runTest { + val deviceEntryFromBiometricAuthentication by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + kosmos.fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard: + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricAuthentication).isNull() + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFingerprintUnlockOnLockScreen_sceneContainerEnabled() = + testScope.runTest { + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntrySourceInteractor + + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFingerprintUnlockOnAod_sceneContainerEnabled() = + testScope.runTest { + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(false) + configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true) + configureBiometricUnlockState(alternateBouncerVisible = false, sceneKey = Scenes.Dream) + runCurrent() + + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFingerprintUnlockOnBouncer_sceneContainerEnabled() = + testScope.runTest { + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Bouncer, + ) + runCurrent() + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @EnableSceneContainer @Test - fun noDeviceEntry() = runTest { - val deviceEntryFromBiometricAuthentication by - collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockState( - BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard: - BiometricUnlockSource.FINGERPRINT_SENSOR, + fun deviceEntryFromFingerprintUnlockOnShade_sceneContainerEnabled() = + testScope.runTest { + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFingerprintUnlockOnAlternateBouncer_sceneContainerEnabled() = + testScope.runTest { + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = true, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnLockScreen_bypassAvailable_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntrySourceInteractor + + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnLockScreen_bypassDisabled_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntrySourceInteractor + + collectLastValue(kosmos.keyguardBypassRepository.isBypassAvailable) + runCurrent() + + val postureControllerCallback = kosmos.devicePostureController.verifyCallback() + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + // MODE_NONE does not dismiss keyguard + assertThat(deviceEntryFromBiometricSource).isNull() + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnBouncer_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Bouncer, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnShade_bypassAvailable_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState(alternateBouncerVisible = false, sceneKey = Scenes.Shade) + runCurrent() + + // MODE_NONE does not dismiss keyguard + assertThat(deviceEntryFromBiometricSource).isNull() + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnShade_bypassDisabled_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = false) + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = false, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource).isNull() + } + + @EnableSceneContainer + @Test + fun deviceEntryFromFaceUnlockOnAlternateBouncer_sceneContainerEnabled() = + testScope.runTest { + kosmos.configureKeyguardBypass(isBypassAvailable = true) + underTest = kosmos.deviceEntrySourceInteractor + val deviceEntryFromBiometricSource by + collectLastValue(underTest.deviceEntryFromBiometricSource) + + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true) + configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true) + configureBiometricUnlockState( + alternateBouncerVisible = true, + sceneKey = Scenes.Lockscreen, + ) + runCurrent() + + assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + private fun configureDeviceEntryBiometricAuthSuccessState( + isFingerprintAuth: Boolean = false, + isFaceAuth: Boolean = false, + ) { + if (isFingerprintAuth) { + val successStatus = SuccessFingerprintAuthenticationStatus(0, true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(successStatus) + } + + if (isFaceAuth) { + val successStatus: FaceAuthenticationStatus = + SuccessFaceAuthenticationStatus( + FaceManager.AuthenticationResult(null, null, 0, true) + ) + kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(successStatus) + } + } + + private fun configureBiometricUnlockState( + alternateBouncerVisible: Boolean, + sceneKey: SceneKey, + ) { + kosmos.keyguardBouncerRepository.setAlternateVisible(alternateBouncerVisible) + kosmos.sceneInteractor.changeScene(sceneKey, "reason") + kosmos.sceneInteractor.setTransitionState( + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(sceneKey)) ) - runCurrent() - assertThat(deviceEntryFromBiometricAuthentication).isNull() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index a8bb2b0aca56..46d1ebe75899 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManagerFake +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig @@ -54,6 +55,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.cameraLauncher import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController @@ -117,8 +119,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" + - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET, + ), ) repository = FakeKeyguardRepository() @@ -141,13 +143,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { context = context, userFileManager = mock<UserFileManager>().apply { - whenever( - getSharedPreferences( - anyString(), - anyInt(), - anyInt(), - ) - ) + whenever(getSharedPreferences(anyString(), anyInt(), anyInt())) .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, @@ -181,10 +177,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { featureFlags = FakeFeatureFlags() val withDeps = - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - repository = repository, - ) + KeyguardInteractorFactory.create(featureFlags = featureFlags, repository = repository) underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = withDeps.keyguardInteractor, @@ -205,6 +198,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { appContext = context, sceneInteractor = { kosmos.sceneInteractor }, ) + kosmos.keyguardQuickAffordanceInteractor = underTest whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f)) } @@ -241,9 +235,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { testScope.runTest { val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET quickAccessWallet.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue = @@ -268,9 +260,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) quickAccessWallet.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue by @@ -287,9 +277,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) quickAccessWallet.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue by @@ -305,9 +293,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { testScope.runTest { biometricSettingsRepository.setIsUserInLockdown(true) quickAccessWallet.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue by @@ -323,9 +309,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { testScope.runTest { repository.setIsDozing(true) homeControls.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue = @@ -340,9 +324,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { testScope.runTest { repository.setKeyguardShowing(false) homeControls.setState( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = ICON, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) val collectedValue = @@ -446,7 +428,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { val collectedValue = collectLastValue( underTest.quickAffordanceAlwaysVisible( - KeyguardQuickAffordancePosition.BOTTOM_START, + KeyguardQuickAffordancePosition.BOTTOM_START ) ) assertThat(collectedValue()) @@ -487,10 +469,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Test fun select() = testScope.runTest { - overrideResource( - R.array.config_keyguardQuickAffordanceDefaults, - arrayOf<String>(), - ) + overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>()) homeControls.setState( KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) ) @@ -530,10 +509,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { activationState = ActivationState.NotSupported, ) ) - assertThat(endConfig()) - .isEqualTo( - KeyguardQuickAffordanceModel.Hidden, - ) + assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(underTest.getSelections()) .isEqualTo( mapOf( @@ -543,7 +519,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = homeControls.key, name = homeControls.pickerName(), iconResourceId = homeControls.pickerIconResourceId, - ), + ) ), KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), ) @@ -551,7 +527,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest.select( KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - quickAccessWallet.key + quickAccessWallet.key, ) assertThat(startConfig()) @@ -564,10 +540,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { activationState = ActivationState.NotSupported, ) ) - assertThat(endConfig()) - .isEqualTo( - KeyguardQuickAffordanceModel.Hidden, - ) + assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(underTest.getSelections()) .isEqualTo( mapOf( @@ -577,7 +550,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = quickAccessWallet.key, name = quickAccessWallet.pickerName(), iconResourceId = quickAccessWallet.pickerIconResourceId, - ), + ) ), KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), ) @@ -614,7 +587,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = quickAccessWallet.key, name = quickAccessWallet.pickerName(), iconResourceId = quickAccessWallet.pickerIconResourceId, - ), + ) ), KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to listOf( @@ -622,7 +595,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = qrCodeScanner.key, name = qrCodeScanner.pickerName(), iconResourceId = qrCodeScanner.pickerIconResourceId, - ), + ) ), ) ) @@ -653,10 +626,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key) underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key) - assertThat(startConfig()) - .isEqualTo( - KeyguardQuickAffordanceModel.Hidden, - ) + assertThat(startConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(endConfig()) .isEqualTo( KeyguardQuickAffordanceModel.Visible( @@ -677,24 +647,18 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = quickAccessWallet.key, name = quickAccessWallet.pickerName(), iconResourceId = quickAccessWallet.pickerIconResourceId, - ), + ) ), ) ) underTest.unselect( KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - quickAccessWallet.key + quickAccessWallet.key, ) - assertThat(startConfig()) - .isEqualTo( - KeyguardQuickAffordanceModel.Hidden, - ) - assertThat(endConfig()) - .isEqualTo( - KeyguardQuickAffordanceModel.Hidden, - ) + assertThat(startConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden) + assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(underTest.getSelections()) .isEqualTo( mapOf<String, List<String>>( @@ -768,15 +732,12 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { id = quickAccessWallet.key, name = quickAccessWallet.pickerName(), iconResourceId = quickAccessWallet.pickerIconResourceId, - ), + ) ), ) ) - underTest.unselect( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - null, - ) + underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, null) assertThat(underTest.getSelections()) .isEqualTo( @@ -787,15 +748,29 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { ) } + @EnableSceneContainer + @Test + fun updatesLaunchingAffordanceFromCameraLauncher() = + testScope.runTest { + val launchingAffordance by collectLastValue(underTest.launchingAffordance) + runCurrent() + + kosmos.cameraLauncher.setLaunchingAffordance(true) + runCurrent() + assertThat(launchingAffordance).isTrue() + + kosmos.cameraLauncher.setLaunchingAffordance(false) + runCurrent() + assertThat(launchingAffordance).isFalse() + } + companion object { private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 private val ICON: Icon = Icon.Resource( res = CONTENT_DESCRIPTION_RESOURCE_ID, contentDescription = - ContentDescription.Resource( - res = CONTENT_DESCRIPTION_RESOURCE_ID, - ), + ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 12eadfcd2539..b5e670c4bbcc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.parameterizeSceneContainerFlag import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -82,6 +83,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() private val communalRepository by lazy { kosmos.communalSceneRepository } private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository } + private val pulseExpansionInteractor by lazy { kosmos.pulseExpansionInteractor } private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor } private val dozeParameters by lazy { kosmos.dozeParameters } private val shadeTestUtil by lazy { kosmos.shadeTestUtil } @@ -188,7 +190,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - notificationsKeyguardInteractor.setPulseExpanding(true) + pulseExpansionInteractor.setPulseExpanding(true) deviceEntryRepository.setBypassEnabled(false) runCurrent() @@ -200,7 +202,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(true) notificationsKeyguardInteractor.setNotificationsFullyHidden(true) runCurrent() @@ -219,7 +221,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() to = KeyguardState.DOZING, testScope, ) - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(false) notificationsKeyguardInteractor.setNotificationsFullyHidden(true) @@ -239,7 +241,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() to = KeyguardState.DOZING, testScope, ) - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(true) @@ -260,7 +262,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() to = KeyguardState.DOZING, testScope, ) - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) @@ -276,7 +278,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(true) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) @@ -298,7 +300,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() testScope.runTest { val isVisible by collectLastValue(underTest.isNotifIconContainerVisible) runCurrent() - notificationsKeyguardInteractor.setPulseExpanding(false) + pulseExpansionInteractor.setPulseExpanding(false) deviceEntryRepository.setBypassEnabled(false) whenever(dozeParameters.alwaysOn).thenReturn(true) whenever(dozeParameters.displayNeedsBlanking).thenReturn(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 499592051731..2c8f7cf47723 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -32,6 +32,7 @@ import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.uiEventLoggerFake import com.android.internal.policy.IKeyguardDismissCallback import com.android.keyguard.AuthInteractionProperties +import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository @@ -71,8 +72,6 @@ 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 @@ -128,6 +127,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -159,7 +159,8 @@ class SceneContainerStartableTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - + whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) + .thenReturn(true) underTest = kosmos.sceneContainerStartable } @@ -405,10 +406,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) underTest.start() - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - + updateFingerprintAuthStatus(isSuccess = true) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -430,10 +428,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - + updateFingerprintAuthStatus(isSuccess = true) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) assertThat(alternateBouncerVisible).isFalse() } @@ -464,9 +459,7 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings) - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings) @@ -501,10 +494,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Lockscreen) - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - + updateFingerprintAuthStatus(isSuccess = true) assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings) assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Gone) } @@ -535,10 +525,7 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - + updateFingerprintAuthStatus(isSuccess = true) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -554,10 +541,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - + updateFingerprintAuthStatus(isSuccess = true) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -592,9 +576,7 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade) assertThat(currentSceneKey).isEqualTo(Scenes.Shade) - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.Shade) @@ -786,6 +768,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -795,24 +778,19 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() underTest.start() - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper) - .vibrateAuthSuccess( - "SceneContainerStartable, $currentSceneKey device-entry::success" - ) + verify(vibratorHelper).vibrateAuthSuccess(anyString()) verify(vibratorHelper, never()).vibrateAuthError(anyString()) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_udfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -822,21 +800,19 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() underTest.start() - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -847,24 +823,19 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps() - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper) - .vibrateAuthSuccess( - "SceneContainerStartable, $currentSceneKey device-entry::success" - ) + verify(vibratorHelper).vibrateAuthSuccess(anyString()) verify(vibratorHelper, never()).vibrateAuthError(anyString()) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -875,15 +846,12 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps() - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -902,8 +870,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(playErrorHaptic).isNotNull() assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper) - .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error") + verify(vibratorHelper).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @@ -943,8 +910,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(playErrorHaptic).isNotNull() assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper) - .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error") + verify(vibratorHelper).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @@ -972,6 +938,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonDown_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -982,24 +949,19 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps(isPowerButtonDown = true) - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper, never()) - .vibrateAuthSuccess( - "SceneContainerStartable, $currentSceneKey device-entry::success" - ) + verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) verify(vibratorHelper, never()).vibrateAuthError(anyString()) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsMSDLSuccessHaptics_whenPowerButtonDown_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -1010,21 +972,19 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps(isPowerButtonDown = true) - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isNull() assertThat(msdlPlayer.latestPropertiesPlayed).isNull() - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -1035,24 +995,19 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps(lastPowerPress = 50) - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper, never()) - .vibrateAuthSuccess( - "SceneContainerStartable, $currentSceneKey device-entry::success" - ) + verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) verify(vibratorHelper, never()).vibrateAuthError(anyString()) - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsMSDLSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = testScope.runTest { + whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) @@ -1063,15 +1018,12 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() allowHapticsOnSfps(lastPowerPress = 50) - unlockWithFingerprintAuth() + // unlock with fingerprint + updateFingerprintAuthStatus(isSuccess = true) assertThat(playSuccessHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isNull() assertThat(msdlPlayer.latestPropertiesPlayed).isNull() - - updateFingerprintAuthStatus(isSuccess = true) - assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @Test @@ -1090,9 +1042,7 @@ class SceneContainerStartableTest : SysuiTestCase() { updateFingerprintAuthStatus(isSuccess = false) assertThat(playErrorHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper, never()) - .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error") + verify(vibratorHelper, never()).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @@ -1112,7 +1062,6 @@ class SceneContainerStartableTest : SysuiTestCase() { updateFingerprintAuthStatus(isSuccess = false) assertThat(playErrorHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isNull() assertThat(msdlPlayer.latestPropertiesPlayed).isNull() } @@ -1132,9 +1081,7 @@ class SceneContainerStartableTest : SysuiTestCase() { updateFaceAuthStatus(isSuccess = false) assertThat(playErrorHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper, never()) - .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error") + verify(vibratorHelper, never()).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @@ -1153,7 +1100,6 @@ class SceneContainerStartableTest : SysuiTestCase() { updateFaceAuthStatus(isSuccess = false) assertThat(playErrorHaptic).isNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) assertThat(msdlPlayer.latestTokenPlayed).isNull() assertThat(msdlPlayer.latestPropertiesPlayed).isNull() } @@ -1176,9 +1122,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ) .forEachIndexed { index, sceneKey -> if (sceneKey == Scenes.Gone) { - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() } fakeSceneDataSource.pause() @@ -1334,9 +1278,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() powerInteractor.setAwakeForTest() runCurrent() @@ -1586,9 +1528,7 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).onBouncerShown() - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() @@ -2350,9 +2290,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val dismissCallback: IKeyguardDismissCallback = mock() kosmos.dismissCallbackRegistry.addCallback(dismissCallback) - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) + updateFingerprintAuthStatus(isSuccess = true) runCurrent() kosmos.fakeExecutor.runAllReady() @@ -2641,13 +2579,6 @@ class SceneContainerStartableTest : SysuiTestCase() { 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, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index ea2de2502945..01c17bd6285c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -619,7 +619,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mScreenOffAnimationController, new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()), notifsKeyguardInteractor, - mKosmos.getCommunalInteractor()); + mKosmos.getCommunalInteractor(), + mKosmos.getPulseExpansionInteractor()); mConfigurationController = new ConfigurationControllerImpl(mContext); PulseExpansionHandler expansionHandler = new PulseExpansionHandler( mContext, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 9f752a89b16d..3b5d358f7c2c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer @@ -86,6 +87,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { private var bypassEnabled: Boolean = false private var statusBarState: Int = StatusBarState.KEYGUARD + private fun eased(dozeAmount: Float) = notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount) @@ -119,6 +121,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { logger, kosmos.notificationsKeyguardInteractor, kosmos.communalInteractor, + kosmos.pulseExpansionInteractor, ) statusBarStateCallback = withArgCaptor { verify(statusBarStateController).addCallback(capture()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt index 133a114fed9d..dcc8ecd27b67 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt @@ -58,19 +58,4 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() { assertThat(notifsFullyHidden).isTrue() } - - @Test - fun isPulseExpanding_reflectsRepository() = - testComponent.runTest { - underTest.setPulseExpanding(false) - val isPulseExpanding by collectLastValue(underTest.isPulseExpanding) - runCurrent() - - assertThat(isPulseExpanding).isFalse() - - underTest.setPulseExpanding(true) - runCurrent() - - assertThat(isPulseExpanding).isTrue() - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt new file mode 100644 index 000000000000..c90183df9847 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.configureKeyguardBypass +import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shadeTestUtil +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class KeyguardBypassInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private lateinit var underTest: KeyguardBypassInteractor + + @Test + fun canBypassFalseWhenBypassAvailableFalse() = + testScope.runTest { + initializeDependenciesForCanBypass(skipIsBypassAvailableCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isFalse() + } + + @Test + fun canBypassTrueOnPrimaryBouncerShowing() = + testScope.runTest { + initializeDependenciesForCanBypass(skipBouncerShowingCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isTrue() + } + + @Test + fun canBypassTrueOnAlternateBouncerShowing() = + testScope.runTest { + initializeDependenciesForCanBypass(skipAlternateBouncerShowingCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isTrue() + } + + @Test + fun canBypassFalseWhenNotOnLockscreenScene() = + testScope.runTest { + initializeDependenciesForCanBypass(skipOnLockscreenSceneCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + runCurrent() + assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen) + assertThat(canBypass).isFalse() + } + + @Test + fun canBypassFalseOnLaunchingAffordance() = + testScope.runTest { + initializeDependenciesForCanBypass(skipLaunchingAffordanceCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isFalse() + } + + @Test + fun canBypassFalseOnPulseExpanding() = + testScope.runTest { + initializeDependenciesForCanBypass(skipPulseExpandingCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isFalse() + } + + @Test + fun canBypassFalseOnQsExpanded() = + testScope.runTest { + initializeDependenciesForCanBypass(skipQsExpandedCheck = false) + val canBypass by collectLastValue(underTest.canBypass) + runCurrent() + assertThat(canBypass).isFalse() + } + + // Initializes all canBypass dependencies to opposite of value needed to return + private fun initializeDependenciesForCanBypass( + skipIsBypassAvailableCheck: Boolean = true, + skipBouncerShowingCheck: Boolean = true, + skipAlternateBouncerShowingCheck: Boolean = true, + skipOnLockscreenSceneCheck: Boolean = true, + skipLaunchingAffordanceCheck: Boolean = true, + skipPulseExpandingCheck: Boolean = true, + skipQsExpandedCheck: Boolean = true, + ) { + // !isBypassAvailable false + kosmos.configureKeyguardBypass(isBypassAvailable = skipIsBypassAvailableCheck) + underTest = kosmos.keyguardBypassInteractor + + // bouncerShowing false, !onLockscreenScene false + // !onLockscreenScene false + setScene( + bouncerShowing = !skipBouncerShowingCheck, + onLockscreenScene = skipOnLockscreenSceneCheck, + ) + // alternateBouncerShowing false + setAlternateBouncerShowing(!skipAlternateBouncerShowingCheck) + // launchingAffordance false + setLaunchingAffordance(!skipLaunchingAffordanceCheck) + // pulseExpanding false + setPulseExpanding(!skipPulseExpandingCheck) + // qsExpanding false + setQsExpanded(!skipQsExpandedCheck) + } + + private fun setAlternateBouncerShowing(alternateBouncerVisible: Boolean) { + kosmos.keyguardBouncerRepository.setAlternateVisible(alternateBouncerVisible) + } + + private fun setScene(bouncerShowing: Boolean, onLockscreenScene: Boolean) { + if (bouncerShowing) { + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "reason") + } else if (onLockscreenScene) { + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + } else { + kosmos.sceneInteractor.changeScene(Scenes.Shade, "reason") + } + } + + private fun setLaunchingAffordance(launchingAffordance: Boolean) { + kosmos.keyguardQuickAffordanceInteractor.setLaunchingAffordance(launchingAffordance) + } + + private fun setPulseExpanding(pulseExpanding: Boolean) { + kosmos.pulseExpansionInteractor.setPulseExpanding(pulseExpanding) + } + + private fun setQsExpanded(qsExpanded: Boolean) { + if (qsExpanded) { + kosmos.shadeTestUtil.setQsExpansion(1f) + } else { + kosmos.shadeTestUtil.setQsExpansion(0f) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 59c8f06a40ad..cb649f28f95b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -70,6 +70,9 @@ import com.android.systemui.haptics.msdl.dagger.MSDLModule; import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; +import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule; +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; import com.android.systemui.keyguard.ui.composable.LockscreenContent; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; @@ -227,6 +230,7 @@ import javax.inject.Named; InputMethodModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, + KeyguardDataQuickAffordanceModule.class, LetterboxModule.class, LogModule.class, MediaProjectionActivitiesModule.class, @@ -428,6 +432,11 @@ public abstract class SystemUIModule { sysuiUiBgExecutor)); } + @Provides + static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() { + return new KeyguardQuickAffordancesMetricsLoggerImpl(); + } + @Binds abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl); diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index 782bce494d11..41a59a959771 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -19,10 +19,12 @@ import com.android.keyguard.logging.BiometricUnlockLogger 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.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.sample import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -46,16 +48,17 @@ import kotlinx.coroutines.flow.onStart class DeviceEntryHapticsInteractor @Inject constructor( - deviceEntrySourceInteractor: DeviceEntrySourceInteractor, - deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + biometricSettingsRepository: BiometricSettingsRepository, deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, + deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + deviceEntrySourceInteractor: DeviceEntrySourceInteractor, fingerprintPropertyRepository: FingerprintPropertyRepository, - biometricSettingsRepository: BiometricSettingsRepository, keyEventInteractor: KeyEventInteractor, + private val logger: BiometricUnlockLogger, powerInteractor: PowerInteractor, private val systemClock: SystemClock, - private val logger: BiometricUnlockLogger, -) { + dumpManager: DumpManager, +) : FlowDumperImpl(dumpManager) { private val powerButtonSideFpsEnrolled = combineTransform( fingerprintPropertyRepository.sensorType, @@ -86,7 +89,7 @@ constructor( powerButtonSideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup, - ::Triple + ::Triple, ) ) .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> @@ -100,14 +103,19 @@ constructor( } allowHaptic } - .map {} // map to Unit + // map to Unit + .map {} + .dumpWhileCollecting("playSuccessHaptic") private val playErrorHapticForBiometricFailure: Flow<Unit> = merge( deviceEntryFingerprintAuthInteractor.fingerprintFailure, deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, ) - .map {} // map to Unit + // map to Unit + .map {} + .dumpWhileCollecting("playErrorHapticForBiometricFailure") + val playErrorHaptic: Flow<Unit> = playErrorHapticForBiometricFailure .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) @@ -118,7 +126,9 @@ constructor( } allowHaptic } - .map {} // map to Unit + // map to Unit + .map {} + .dumpWhileCollecting("playErrorHaptic") private val recentPowerButtonPressThresholdMs = 400L } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt index 0b9336fec946..b2d4405ee6be 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt @@ -16,18 +16,49 @@ package com.android.systemui.deviceentry.domain.interactor +import androidx.annotation.VisibleForTesting +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.biometrics.AuthController +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM +import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING +import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode +import com.android.systemui.statusbar.phone.DozeScrimController +import com.android.systemui.util.kotlin.FlowDumperImpl +import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter +import com.android.systemui.util.kotlin.combine import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge /** * Hosts application business logic related to the source of the user entering the device. Note: The @@ -42,13 +73,184 @@ import kotlinx.coroutines.flow.map class DeviceEntrySourceInteractor @Inject constructor( + authenticationInteractor: AuthenticationInteractor, + authController: AuthController, + alternateBouncerInteractor: AlternateBouncerInteractor, + deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, + deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + dozeScrimController: DozeScrimController, + keyguardBypassInteractor: KeyguardBypassInteractor, + keyguardUpdateMonitor: KeyguardUpdateMonitor, keyguardInteractor: KeyguardInteractor, -) { + sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor, + sceneInteractor: SceneInteractor, + dumpManager: DumpManager, +) : FlowDumperImpl(dumpManager) { + private val isShowingBouncerScene: Flow<Boolean> = + sceneInteractor.transitionState + .map { transitionState -> + transitionState.isIdle(Scenes.Bouncer) || + transitionState.isTransitioning(null, Scenes.Bouncer) + } + .distinctUntilChanged() + .dumpWhileCollecting("isShowingBouncerScene") + + private val isUnlockedWithStrongFaceUnlock = + deviceEntryFaceAuthInteractor.authenticationStatus + .map { status -> + (status as? SuccessFaceAuthenticationStatus)?.successResult?.isStrongBiometric + ?: false + } + .dumpWhileCollecting("unlockedWithStrongFaceUnlock") + + private val isUnlockedWithStrongFingerprintUnlock = + deviceEntryFingerprintAuthInteractor.authenticationStatus + .map { status -> + (status as? SuccessFingerprintAuthenticationStatus)?.isStrongBiometric ?: false + } + .dumpWhileCollecting("unlockedWithStrongFingerprintUnlock") + + private val faceWakeAndUnlockMode: Flow<BiometricUnlockMode> = + combine( + alternateBouncerInteractor.isVisible, + keyguardBypassInteractor.isBypassAvailable, + isUnlockedWithStrongFaceUnlock, + sceneContainerOcclusionInteractor.isOccludingActivityShown, + sceneInteractor.currentScene, + isShowingBouncerScene, + ) { + isAlternateBouncerVisible, + isBypassAvailable, + isFaceStrongBiometric, + isOccluded, + currentScene, + isShowingBouncerScene -> + val isUnlockingAllowed = + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(isFaceStrongBiometric) + val bypass = isBypassAvailable || authController.isUdfpsFingerDown() + + when { + !keyguardUpdateMonitor.isDeviceInteractive -> + when { + !isUnlockingAllowed -> if (bypass) MODE_SHOW_BOUNCER else MODE_NONE + bypass -> MODE_WAKE_AND_UNLOCK_PULSING + else -> MODE_ONLY_WAKE + } + + isUnlockingAllowed && currentScene == Scenes.Dream -> + if (bypass) MODE_WAKE_AND_UNLOCK_FROM_DREAM else MODE_ONLY_WAKE + + isUnlockingAllowed && isOccluded -> MODE_UNLOCK_COLLAPSING + + (isShowingBouncerScene || isAlternateBouncerVisible) && isUnlockingAllowed -> + MODE_DISMISS_BOUNCER + + isUnlockingAllowed && bypass -> MODE_UNLOCK_COLLAPSING + + bypass -> MODE_SHOW_BOUNCER + + else -> MODE_NONE + } + } + .map { biometricModeIntToObject(it) } + .dumpWhileCollecting("faceWakeAndUnlockMode") + + private val fingerprintWakeAndUnlockMode: Flow<BiometricUnlockMode> = + combine( + alternateBouncerInteractor.isVisible, + authenticationInteractor.authenticationMethod, + sceneInteractor.currentScene, + isUnlockedWithStrongFingerprintUnlock, + isShowingBouncerScene, + ) { + alternateBouncerVisible, + authenticationMethod, + currentScene, + isFingerprintStrongBiometric, + isShowingBouncerScene -> + val unlockingAllowed = + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + isFingerprintStrongBiometric + ) + when { + !keyguardUpdateMonitor.isDeviceInteractive -> + when { + dozeScrimController.isPulsing && unlockingAllowed -> + MODE_WAKE_AND_UNLOCK_PULSING + + unlockingAllowed || !authenticationMethod.isSecure -> + MODE_WAKE_AND_UNLOCK + + else -> MODE_SHOW_BOUNCER + } + + unlockingAllowed && currentScene == Scenes.Dream -> + MODE_WAKE_AND_UNLOCK_FROM_DREAM + + isShowingBouncerScene && unlockingAllowed -> MODE_DISMISS_BOUNCER + + unlockingAllowed -> MODE_UNLOCK_COLLAPSING + + currentScene != Scenes.Bouncer && !alternateBouncerVisible -> MODE_SHOW_BOUNCER + + else -> MODE_NONE + } + } + .map { biometricModeIntToObject(it) } + .dumpWhileCollecting("fingerprintWakeAndUnlockMode") + + @VisibleForTesting + private val biometricUnlockStateOnKeyguardDismissed = + merge( + fingerprintWakeAndUnlockMode + .filter { BiometricUnlockMode.dismissesKeyguard(it) } + .map { mode -> + BiometricUnlockModel(mode, BiometricUnlockSource.FINGERPRINT_SENSOR) + }, + faceWakeAndUnlockMode + .filter { BiometricUnlockMode.dismissesKeyguard(it) } + .map { mode -> BiometricUnlockModel(mode, BiometricUnlockSource.FACE_SENSOR) }, + ) + .dumpWhileCollecting("biometricUnlockState") + + private val deviceEntryFingerprintAuthWakeAndUnlockEvents: + Flow<SuccessFingerprintAuthenticationStatus> = + deviceEntryFingerprintAuthInteractor.authenticationStatus + .filterIsInstance<SuccessFingerprintAuthenticationStatus>() + .dumpWhileCollecting("deviceEntryFingerprintAuthSuccessEvents") + + private val deviceEntryFaceAuthWakeAndUnlockEvents: Flow<SuccessFaceAuthenticationStatus> = + deviceEntryFaceAuthInteractor.authenticationStatus + .filterIsInstance<SuccessFaceAuthenticationStatus>() + .sampleFilter( + combine( + sceneContainerOcclusionInteractor.isOccludingActivityShown, + keyguardBypassInteractor.isBypassAvailable, + keyguardBypassInteractor.canBypass, + ::Triple, + ) + ) { (isOccludingActivityShown, isBypassAvailable, canBypass) -> + isOccludingActivityShown || !isBypassAvailable || canBypass + } + .dumpWhileCollecting("deviceEntryFaceAuthSuccessEvents") + + private val deviceEntryBiometricAuthSuccessEvents = + merge(deviceEntryFingerprintAuthWakeAndUnlockEvents, deviceEntryFaceAuthWakeAndUnlockEvents) + .dumpWhileCollecting("deviceEntryBiometricAuthSuccessEvents") + val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> = - keyguardInteractor.biometricUnlockState - .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) } - .map { it.source } - .filterNotNull() + if (SceneContainerFlag.isEnabled) { + deviceEntryBiometricAuthSuccessEvents + .sample(biometricUnlockStateOnKeyguardDismissed) + .map { it.source } + .filterNotNull() + } else { + keyguardInteractor.biometricUnlockState + .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) } + .map { it.source } + .filterNotNull() + } + .dumpWhileCollecting("deviceEntryFromBiometricSource") private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow() val deviceEntryFromDeviceEntryIcon: Flow<Unit> = @@ -60,4 +262,18 @@ constructor( suspend fun attemptEnterDeviceFromDeviceEntryIcon() { attemptEnterDeviceFromDeviceEntryIcon.emit(Unit) } + + private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode { + return when (value) { + MODE_NONE -> BiometricUnlockMode.NONE + MODE_WAKE_AND_UNLOCK -> BiometricUnlockMode.WAKE_AND_UNLOCK + MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING + MODE_SHOW_BOUNCER -> BiometricUnlockMode.SHOW_BOUNCER + MODE_ONLY_WAKE -> BiometricUnlockMode.ONLY_WAKE + MODE_UNLOCK_COLLAPSING -> BiometricUnlockMode.UNLOCK_COLLAPSING + MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM + MODE_DISMISS_BOUNCER -> BiometricUnlockMode.DISMISS_BOUNCER + else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value") + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index d0a40ec3a361..7638079d7475 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -61,8 +61,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; -import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; -import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule; import com.android.systemui.log.SessionTracker; @@ -239,12 +237,6 @@ public interface KeyguardModule { /** */ @Provides - static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() { - return new KeyguardQuickAffordancesMetricsLoggerImpl(); - } - - /** */ - @Provides @SysUISingleton static ThreadAssert providesThreadAssert() { return new ThreadAssert(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index e68d79937063..4d999df69588 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -106,7 +106,7 @@ constructor( trySendWithFailureLogging( getFpSensorType(), TAG, - "onAllAuthenticatorsRegistered, emitting fpSensorType" + "onAllAuthenticatorsRegistered, emitting fpSensorType", ) } } @@ -114,7 +114,7 @@ constructor( trySendWithFailureLogging( getFpSensorType(), TAG, - "initial value for fpSensorType" + "initial value for fpSensorType", ) awaitClose { authController.removeCallback(callback) } } @@ -134,7 +134,7 @@ constructor( trySendWithFailureLogging( keyguardUpdateMonitor.isFingerprintLockedOut, TAG, - "onLockedOutStateChanged" + "onLockedOutStateChanged", ) } val callback = @@ -154,7 +154,7 @@ constructor( .stateIn( scope, started = Eagerly, - initialValue = keyguardUpdateMonitor.isFingerprintLockedOut + initialValue = keyguardUpdateMonitor.isFingerprintLockedOut, ) } @@ -165,13 +165,13 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onBiometricRunningStateChanged( running: Boolean, - biometricSourceType: BiometricSourceType? + biometricSourceType: BiometricSourceType?, ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { trySendWithFailureLogging( running, TAG, - "Fingerprint running state changed" + "Fingerprint running state changed", ) } } @@ -180,7 +180,7 @@ constructor( trySendWithFailureLogging( keyguardUpdateMonitor.isFingerprintDetectionRunning, TAG, - "Initial fingerprint running state" + "Initial fingerprint running state", ) awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } @@ -193,11 +193,7 @@ constructor( .map { it.isEngaged } .filterNotNull() .map { it } - .stateIn( - scope = scope, - started = WhileSubscribed(), - initialValue = false, - ) + .stateIn(scope = scope, started = WhileSubscribed(), initialValue = false) // TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages // in BiometricStatusRepository @@ -232,10 +228,7 @@ constructor( ) { sendUpdateIfFingerprint( biometricSourceType, - ErrorFingerprintAuthenticationStatus( - msgId, - errString, - ), + ErrorFingerprintAuthenticationStatus(msgId, errString), ) } @@ -246,15 +239,12 @@ constructor( ) { sendUpdateIfFingerprint( biometricSourceType, - HelpFingerprintAuthenticationStatus( - msgId, - helpString, - ), + HelpFingerprintAuthenticationStatus(msgId, helpString), ) } override fun onBiometricAuthFailed( - biometricSourceType: BiometricSourceType, + biometricSourceType: BiometricSourceType ) { sendUpdateIfFingerprint( biometricSourceType, @@ -270,14 +260,14 @@ constructor( biometricSourceType, AcquiredFingerprintAuthenticationStatus( AuthenticationReason.DeviceEntryAuthentication, - acquireInfo + acquireInfo, ), ) } private fun sendUpdateIfFingerprint( biometricSourceType: BiometricSourceType, - authenticationStatus: FingerprintAuthenticationStatus + authenticationStatus: FingerprintAuthenticationStatus, ) { if (biometricSourceType != BiometricSourceType.FINGERPRINT) { return @@ -285,13 +275,14 @@ constructor( trySendWithFailureLogging( authenticationStatus, TAG, - "new fingerprint authentication status" + "new fingerprint authentication status", ) } } keyguardUpdateMonitor.registerCallback(callback) awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } + .flowOn(mainDispatcher) .buffer(capacity = 4) override val shouldUpdateIndicatorVisibility: Flow<Boolean> = @@ -302,7 +293,7 @@ constructor( shouldUpdateIndicatorVisibility, TAG, "Error sending shouldUpdateIndicatorVisibility " + - "$shouldUpdateIndicatorVisibility" + "$shouldUpdateIndicatorVisibility", ) } @@ -310,7 +301,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onBiometricRunningStateChanged( running: Boolean, - biometricSourceType: BiometricSourceType? + biometricSourceType: BiometricSourceType?, ) { sendShouldUpdateIndicatorVisibility(true) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt new file mode 100644 index 000000000000..be4ab4b2c486 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.annotation.IntDef +import android.content.res.Resources +import android.provider.Settings +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.keyguard.shared.model.DevicePosture.UNKNOWN +import com.android.systemui.res.R +import com.android.systemui.tuner.TunerService +import com.android.systemui.util.kotlin.FlowDumperImpl +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +@SysUISingleton +class KeyguardBypassRepository +@Inject +constructor( + @Main resources: Resources, + biometricSettingsRepository: BiometricSettingsRepository, + devicePostureRepository: DevicePostureRepository, + dumpManager: DumpManager, + private val tunerService: TunerService, + @Background backgroundDispatcher: CoroutineDispatcher, +) : FlowDumperImpl(dumpManager) { + + @get:BypassOverride + private val bypassOverride: Int by lazy { + resources.getInteger(R.integer.config_face_unlock_bypass_override) + } + + private val configFaceAuthSupportedPosture: DevicePosture by lazy { + DevicePosture.toPosture(resources.getInteger(R.integer.config_face_auth_supported_posture)) + } + + private val dismissByDefault: Int by lazy { + if (resources.getBoolean(com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) { + 1 + } else { + 0 + } + } + + private var bypassEnabledSetting: Flow<Boolean> = + callbackFlow { + val updateBypassSetting = { state: Boolean -> + trySendWithFailureLogging(state, TAG, "Error sending bypassSetting $state") + } + + val tunable = + TunerService.Tunable { key, _ -> + updateBypassSetting(tunerService.getValue(key, dismissByDefault) != 0) + } + + updateBypassSetting(false) + tunerService.addTunable(tunable, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD) + awaitClose { tunerService.removeTunable(tunable) } + } + .flowOn(backgroundDispatcher) + .dumpWhileCollecting("bypassEnabledSetting") + + val overrideFaceBypassSetting: Flow<Boolean> = + when (bypassOverride) { + FACE_UNLOCK_BYPASS_ALWAYS -> flowOf(true) + FACE_UNLOCK_BYPASS_NEVER -> flowOf(false) + else -> bypassEnabledSetting + } + + val isPostureAllowedForFaceAuth: Flow<Boolean> = + when (configFaceAuthSupportedPosture) { + UNKNOWN -> flowOf(true) + else -> + devicePostureRepository.currentDevicePosture + .map { posture -> posture == configFaceAuthSupportedPosture } + .distinctUntilChanged() + } + + /** + * Whether bypass is available. + * + * Bypass is the ability to skip the lockscreen when the device is unlocked using non-primary + * authentication types like face unlock, instead of requiring the user to explicitly dismiss + * the lockscreen by swiping after the device is already unlocked. + * + * "Available" refers to a combination of the user setting to skip the lockscreen being set, + * whether hard-wired OEM-overridable configs allow the feature, whether a foldable is in the + * right foldable posture, and other such things. It does _not_ model this based on more + * runtime-like states of the UI. + */ + val isBypassAvailable: Flow<Boolean> = + combine( + overrideFaceBypassSetting, + biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, + isPostureAllowedForFaceAuth, + ) { + bypassOverride: Boolean, + isFaceEnrolledAndEnabled: Boolean, + isPostureAllowedForFaceAuth: Boolean -> + bypassOverride && isFaceEnrolledAndEnabled && isPostureAllowedForFaceAuth + } + .distinctUntilChanged() + .dumpWhileCollecting("isBypassAvailable") + + @IntDef(FACE_UNLOCK_BYPASS_NO_OVERRIDE, FACE_UNLOCK_BYPASS_ALWAYS, FACE_UNLOCK_BYPASS_NEVER) + @Retention(AnnotationRetention.SOURCE) + private annotation class BypassOverride + + companion object { + private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0 + private const val FACE_UNLOCK_BYPASS_ALWAYS = 1 + private const val FACE_UNLOCK_BYPASS_NEVER = 2 + + private const val TAG = "KeyguardBypassRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index d49550ef4c83..d0de21b45be0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -36,12 +36,14 @@ import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerR import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.util.kotlin.FlowDumperImpl import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -64,7 +66,14 @@ constructor( configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>, dumpManager: DumpManager, userHandle: UserHandle, -) { +) : FlowDumperImpl(dumpManager) { + /** + * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI + * elements that allow the user to perform quick actions without unlocking their device. + */ + val launchingAffordance: MutableStateFlow<Boolean> = + MutableStateFlow(false).dumpValue("launchingAffordance") + // Configs for all keyguard quick affordances, mapped by the quick affordance ID as key private val configsByAffordanceId: Map<String, KeyguardQuickAffordanceConfig> = configs.associateBy { it.key } @@ -112,11 +121,7 @@ constructor( } } } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = emptyMap(), - ) + .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = emptyMap()) init { legacySettingSyncer.startSyncing() @@ -144,14 +149,8 @@ constructor( * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance * IDs should be descending priority order. */ - fun setSelections( - slotId: String, - affordanceIds: List<String>, - ) { - selectionManager.value.setSelections( - slotId = slotId, - affordanceIds = affordanceIds, - ) + fun setSelections(slotId: String, affordanceIds: List<String>) { + selectionManager.value.setSelections(slotId = slotId, affordanceIds = affordanceIds) } /** @@ -222,10 +221,7 @@ constructor( val (slotId, slotCapacity) = parseSlot(unparsedSlot) check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" } seenSlotIds.add(slotId) - KeyguardSlotPickerRepresentation( - id = slotId, - maxSelectedAffordances = slotCapacity, - ) + KeyguardSlotPickerRepresentation(id = slotId, maxSelectedAffordances = slotCapacity) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt new file mode 100644 index 000000000000..7699bab12785 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.kotlin.FlowDumperImpl +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class PulseExpansionRepository @Inject constructor(dumpManager: DumpManager) : + FlowDumperImpl(dumpManager) { + /** + * Whether the notification panel is expanding from the user swiping downward on a notification + * from the pulsing state, or swiping anywhere on the screen when face bypass is enabled + */ + val isPulseExpanding: MutableStateFlow<Boolean> = + MutableStateFlow(false).dumpValue("pulseExpanding") +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt new file mode 100644 index 000000000000..d793064f918e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.data.repository.KeyguardBypassRepository +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.util.kotlin.FlowDumperImpl +import com.android.systemui.util.kotlin.combine +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +@SysUISingleton +class KeyguardBypassInteractor +@Inject +constructor( + keyguardBypassRepository: KeyguardBypassRepository, + alternateBouncerInteractor: AlternateBouncerInteractor, + keyguardQuickAffordanceInteractor: KeyguardQuickAffordanceInteractor, + pulseExpansionInteractor: PulseExpansionInteractor, + sceneInteractor: SceneInteractor, + shadeInteractor: ShadeInteractor, + dumpManager: DumpManager, +) : FlowDumperImpl(dumpManager) { + + /** + * Whether bypassing the keyguard is enabled by the user in user settings (skipping the + * lockscreen when authenticating using secondary authentication types like face unlock). + */ + val isBypassAvailable: Flow<Boolean> = + keyguardBypassRepository.isBypassAvailable.dumpWhileCollecting("isBypassAvailable") + + /** + * Models whether bypass is unavailable (no secondary authentication types enrolled), or if the + * keyguard can be bypassed as a combination of the settings toggle value set by the user and + * other factors related to device state. + */ + val canBypass: Flow<Boolean> = + isBypassAvailable + .flatMapLatest { isBypassAvailable -> + if (isBypassAvailable) { + combine( + sceneInteractor.currentScene.map { scene -> scene == Scenes.Bouncer }, + alternateBouncerInteractor.isVisible, + sceneInteractor.currentScene.map { scene -> scene == Scenes.Lockscreen }, + keyguardQuickAffordanceInteractor.launchingAffordance, + pulseExpansionInteractor.isPulseExpanding, + shadeInteractor.isQsExpanded, + ) { + isBouncerShowing, + isAlternateBouncerShowing, + isOnLockscreenScene, + isLaunchingAffordance, + isPulseExpanding, + isQsExpanded -> + when { + isBouncerShowing -> true + isAlternateBouncerShowing -> true + !isOnLockscreenScene -> false + isLaunchingAffordance -> false + isPulseExpanding -> false + isQsExpanded -> false + else -> true + } + } + } else { + flowOf(false) + } + } + .dumpWhileCollecting("canBypass") + + companion object { + private const val TAG: String = "KeyguardBypassInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 26bf26b34a8a..21afd3e4c444 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -61,6 +61,8 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest @@ -91,6 +93,11 @@ constructor( @Application private val appContext: Context, private val sceneInteractor: Lazy<SceneInteractor>, ) { + /** + * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI + * elements that allow the user to perform quick actions without unlocking their device. + */ + val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow() /** * Whether the UI should use the long press gesture to activate quick affordances. @@ -167,11 +174,7 @@ constructor( * @param expandable An optional [Expandable] for the activity- or dialog-launch animation * @param slotId The id of the lockscreen slot that the affordance is in */ - fun onQuickAffordanceTriggered( - configKey: String, - expandable: Expandable?, - slotId: String, - ) { + fun onQuickAffordanceTriggered(configKey: String, expandable: Expandable?, slotId: String) { val (decodedSlotId, decodedConfigKey) = configKey.decode() val config = repository.get().selections.value[decodedSlotId]?.find { it.key == decodedConfigKey } @@ -191,10 +194,7 @@ constructor( ) is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> - showDialog( - result.dialog, - result.expandable, - ) + showDialog(result.dialog, result.expandable) } } @@ -225,12 +225,7 @@ constructor( selections.add(affordanceId) - repository - .get() - .setSelections( - slotId = slotId, - affordanceIds = selections, - ) + repository.get().setSelections(slotId = slotId, affordanceIds = selections) logger.logQuickAffordanceSelected(slotId, affordanceId) metricsLogger.logOnShortcutSelected(slotId, affordanceId) @@ -274,12 +269,7 @@ constructor( .getOrDefault(slotId, emptyList()) .toMutableList() return if (selections.remove(affordanceId)) { - repository - .get() - .setSelections( - slotId = slotId, - affordanceIds = selections, - ) + repository.get().setSelections(slotId = slotId, affordanceIds = selections) true } else { false @@ -399,11 +389,15 @@ constructor( intent, true /* dismissShade */, expandable?.activityTransitionController(), - true /* showOverLockscreenWhenLocked */, + true, /* showOverLockscreenWhenLocked */ ) } } + fun setLaunchingAffordance(isLaunchingAffordance: Boolean) { + repository.get().launchingAffordance.value = isLaunchingAffordance + } + private fun String.encode(slotId: String): String { return "$slotId$DELIMITER$this" } @@ -444,19 +438,19 @@ constructor( ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME, - value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME) + value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME), ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP, - value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP) + value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP), ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS, - value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS) + value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS), ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION, - value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION) + value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION), ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt new file mode 100644 index 000000000000..377d7eaaddcc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.data.repository.PulseExpansionRepository +import com.android.systemui.util.kotlin.FlowDumperImpl +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class PulseExpansionInteractor +@Inject +constructor(private val repository: PulseExpansionRepository, dumpManager: DumpManager) : + FlowDumperImpl(dumpManager) { + /** + * Whether the notification panel is expanding from the user swiping downward on a notification + * from the pulsing state, or swiping anywhere on the screen when face bypass is enabled + */ + val isPulseExpanding: StateFlow<Boolean> = + repository.isPulseExpanding.asStateFlow().dumpValue("isPulseExpanding") + + /** Updates whether a pulse expansion is occurring. */ + fun setPulseExpanding(pulseExpanding: Boolean) { + repository.isPulseExpanding.value = pulseExpanding + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 40d4193202d2..0d816041d1be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -81,6 +82,7 @@ constructor( private val communalInteractor: CommunalInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, + private val pulseExpansionInteractor: PulseExpansionInteractor, notificationShadeWindowModel: NotificationShadeWindowModel, private val aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, @@ -371,7 +373,7 @@ constructor( /** Is there an expanded pulse, are we animating in response? */ private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> { - return notificationsKeyguardInteractor.isPulseExpanding + return pulseExpansionInteractor.isPulseExpanding .pairwise(initialValue = null) // If pulsing changes, start animating, unless it's the first emission .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java index fc61e90ab8f7..1d81e4083a35 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java +++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java @@ -18,25 +18,33 @@ package com.android.systemui.shade; import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import dagger.Lazy; + import javax.inject.Inject; + /** Handles launching camera from Shade. */ @SysUISingleton public class CameraLauncher { private final CameraGestureHelper mCameraGestureHelper; private final KeyguardBypassController mKeyguardBypassController; + private final Lazy<KeyguardQuickAffordanceInteractor> mKeyguardQuickAffordanceInteractorLazy; private boolean mLaunchingAffordance; @Inject public CameraLauncher( CameraGestureHelper cameraGestureHelper, - KeyguardBypassController keyguardBypassController + KeyguardBypassController keyguardBypassController, + Lazy<KeyguardQuickAffordanceInteractor> keyguardQuickAffordanceInteractorLazy ) { mCameraGestureHelper = cameraGestureHelper; mKeyguardBypassController = keyguardBypassController; + mKeyguardQuickAffordanceInteractorLazy = keyguardQuickAffordanceInteractorLazy; } /** Launches the camera. */ @@ -54,7 +62,12 @@ public class CameraLauncher { */ public void setLaunchingAffordance(boolean launchingAffordance) { mLaunchingAffordance = launchingAffordance; - mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); + if (SceneContainerFlag.isEnabled()) { + mKeyguardQuickAffordanceInteractorLazy.get() + .setLaunchingAffordance(launchingAffordance); + } else { + mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index ea515e0c2cf1..08ffbf2b29d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -22,11 +22,13 @@ import androidx.annotation.VisibleForTesting import androidx.core.animation.ObjectAnimator import com.android.app.animation.Interpolators import com.android.app.animation.InterpolatorsAndroidX +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Dumpable import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionListener @@ -49,7 +51,6 @@ import javax.inject.Inject import kotlin.math.max import kotlin.math.min import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class NotificationWakeUpCoordinator @@ -65,6 +66,7 @@ constructor( private val logger: NotificationWakeUpCoordinatorLogger, private val notifsKeyguardInteractor: NotificationsKeyguardInteractor, private val communalInteractor: CommunalInteractor, + private val pulseExpansionInteractor: PulseExpansionInteractor, ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, @@ -115,7 +117,7 @@ constructor( // they were blocked by the proximity sensor updateNotificationVisibility( animate = shouldAnimateVisibility(), - increaseSpeed = false + increaseSpeed = false, ) } } @@ -139,7 +141,7 @@ constructor( // the waking up callback updateNotificationVisibility( animate = shouldAnimateVisibility(), - increaseSpeed = false + increaseSpeed = false, ) } } @@ -200,7 +202,7 @@ constructor( setNotificationsVisibleForExpansion( visible = false, animate = false, - increaseSpeed = false + increaseSpeed = false, ) } } @@ -226,7 +228,7 @@ constructor( for (listener in wakeUpListeners) { listener.onPulseExpandingChanged(pulseExpanding) } - notifsKeyguardInteractor.setPulseExpanding(pulseExpanding) + pulseExpansionInteractor.setPulseExpanding(pulseExpanding) } } } @@ -241,7 +243,7 @@ constructor( fun setNotificationsVisibleForExpansion( visible: Boolean, animate: Boolean, - increaseSpeed: Boolean + increaseSpeed: Boolean, ) { notificationsVisibleForExpansion = visible updateNotificationVisibility(animate, increaseSpeed) @@ -282,7 +284,7 @@ constructor( private fun setNotificationsVisible( visible: Boolean, animate: Boolean, - increaseSpeed: Boolean + increaseSpeed: Boolean, ) { if (notificationsVisible == visible) { return @@ -363,7 +365,7 @@ constructor( hardOverride = hardDozeAmountOverride, outputLinear = outputLinearDozeAmount, state = statusBarStateController.state, - changed = changed + changed = changed, ) stackScrollerController.setDozeAmount(outputEasedDozeAmount) updateHideAmount() @@ -372,7 +374,7 @@ constructor( setNotificationsVisibleForExpansion( visible = false, animate = false, - increaseSpeed = false + increaseSpeed = false, ) } } @@ -389,10 +391,7 @@ constructor( * call with `false` at some point in the near future. A call with `true` before that will * happen if the animation is not already running. */ - fun setWakingUp( - wakingUp: Boolean, - requestDelayedAnimation: Boolean, - ) { + fun setWakingUp(wakingUp: Boolean, requestDelayedAnimation: Boolean) { logger.logSetWakingUp(wakingUp, requestDelayedAnimation) this.wakingUp = wakingUp if (wakingUp && requestDelayedAnimation) { @@ -432,7 +431,7 @@ constructor( // See: UnlockedScreenOffAnimationController.onFinishedWakingUp() setHardDozeAmountOverride( dozing = false, - source = "Override: Shade->Shade (lock cancelled by unlock)" + source = "Override: Shade->Shade (lock cancelled by unlock)", ) this.state = newState return @@ -478,7 +477,7 @@ constructor( wasCollapsedEnoughToHide, isCollapsedEnoughToHide, couldShowPulsingHuns, - canShowPulsingHuns + canShowPulsingHuns, ) if (couldShowPulsingHuns && !canShowPulsingHuns) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt index bd6ea30c44e6..f9fd5caa0d25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt @@ -24,7 +24,4 @@ import kotlinx.coroutines.flow.MutableStateFlow class NotificationsKeyguardViewStateRepository @Inject constructor() { /** Are notifications fully hidden from view? */ val areNotificationsFullyHidden = MutableStateFlow(false) - - /** Is a pulse expansion occurring? */ - val isPulseExpanding = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt index a6361cbc9f9c..1cb41448f257 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt @@ -22,12 +22,7 @@ import kotlinx.coroutines.flow.Flow /** Domain logic pertaining to notifications on the keyguard. */ class NotificationsKeyguardInteractor @Inject -constructor( - private val repository: NotificationsKeyguardViewStateRepository, -) { - /** Is a pulse expansion occurring? */ - val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding - +constructor(private val repository: NotificationsKeyguardViewStateRepository) { /** Are notifications fully hidden from view? */ val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden @@ -35,9 +30,4 @@ constructor( fun setNotificationsFullyHidden(fullyHidden: Boolean) { repository.areNotificationsFullyHidden.value = fullyHidden } - - /** Updates whether a pulse expansion is occurring. */ - fun setPulseExpanding(pulseExpanding: Boolean) { - repository.isPulseExpanding.value = pulseExpanding - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt new file mode 100644 index 000000000000..0c0b5baad821 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.data.repository + +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.KeyguardBypassRepository +import com.android.systemui.keyguard.data.repository.configureKeyguardBypass +import com.android.systemui.keyguard.data.repository.keyguardBypassRepository +import com.android.systemui.keyguard.data.repository.verifyCallback +import com.android.systemui.keyguard.data.repository.verifyNoCallback +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.devicePostureController +import com.android.systemui.testKosmos +import com.android.systemui.tuner.TunerService +import com.android.systemui.tuner.tunerService +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class KeyguardBypassRepositoryTest : SysuiTestCase() { + @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule() + + private lateinit var tunableCallback: TunerService.Tunable + private lateinit var postureControllerCallback: DevicePostureController.Callback + + private val kosmos = testKosmos() + private lateinit var underTest: KeyguardBypassRepository + private val testScope = kosmos.testScope + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + // overrideFaceBypassSetting overridden to true + // isFaceEnrolledAndEnabled true + // isPostureAllowedForFaceAuth true/false on posture changes + @Test + fun updatesBypassAvailableOnPostureChanges_bypassOverrideAlways() = + testScope.runTest { + // KeyguardBypassRepository#overrideFaceBypassSetting = true due to ALWAYS override + // Initialize face auth posture to DEVICE_POSTURE_OPENED config + initUnderTest( + faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_ALWAYS, + faceAuthPostureConfig = DEVICE_POSTURE_CLOSED, + ) + val isBypassAvailable by collectLastValue(underTest.isBypassAvailable) + runCurrent() + + postureControllerCallback = kosmos.devicePostureController.verifyCallback() + + // Update face auth posture to match config + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + // Assert bypass available + assertThat(isBypassAvailable).isTrue() + + // Set face auth posture to not match config + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Assert bypass not available + assertThat(isBypassAvailable).isFalse() + } + + // overrideFaceBypassSetting overridden to false + // isFaceEnrolledAndEnabled true + // isPostureAllowedForFaceAuth true/false on posture changes + @Test + fun updatesBypassEnabledOnPostureChanges_bypassOverrideNever() = + testScope.runTest { + // KeyguardBypassRepository#overrideFaceBypassSetting = false due to NEVER override + // Initialize face auth posture to DEVICE_POSTURE_OPENED config + initUnderTest( + faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NEVER, + faceAuthPostureConfig = DEVICE_POSTURE_CLOSED, + ) + val bypassEnabled by collectLastValue(underTest.isBypassAvailable) + runCurrent() + postureControllerCallback = kosmos.devicePostureController.verifyCallback() + + // Update face auth posture to match config + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + // Assert bypass not enabled + assertThat(bypassEnabled).isFalse() + + // Set face auth posture to not match config + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Assert bypass not enabled + assertThat(bypassEnabled).isFalse() + } + + // overrideFaceBypassSetting set true/false depending on Setting + // isFaceEnrolledAndEnabled true + // isPostureAllowedForFaceAuth true + @Test + fun updatesBypassEnabledOnSettingsChanges_bypassNoOverride_devicePostureMatchesConfig() = + testScope.runTest { + // No bypass override + // Initialize face auth posture to DEVICE_POSTURE_OPENED config + initUnderTest( + faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NO_OVERRIDE, + faceAuthPostureConfig = DEVICE_POSTURE_CLOSED, + ) + + val bypassEnabled by collectLastValue(underTest.isBypassAvailable) + runCurrent() + postureControllerCallback = kosmos.devicePostureController.verifyCallback() + tunableCallback = kosmos.tunerService.captureCallback() + + // Update face auth posture to match config + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + // FACE_UNLOCK_DISMISSES_KEYGUARD setting true + whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt())) + .thenReturn(1) + tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "") + + runCurrent() + // Assert bypass enabled + assertThat(bypassEnabled).isTrue() + + // FACE_UNLOCK_DISMISSES_KEYGUARD setting false + whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt())) + .thenReturn(0) + tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "") + + runCurrent() + // Assert bypass not enabled + assertThat(bypassEnabled).isFalse() + } + + // overrideFaceBypassSetting overridden to true + // isFaceEnrolledAndEnabled true + // isPostureAllowedForFaceAuth always true given DEVICE_POSTURE_UNKNOWN config + @Test + fun bypassEnabledTrue_bypassAlways_unknownDevicePostureConfig() = + testScope.runTest { + // KeyguardBypassRepository#overrideFaceBypassSetting = true due to ALWAYS override + // Set face auth posture config to unknown + initUnderTest( + faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_ALWAYS, + faceAuthPostureConfig = DEVICE_POSTURE_UNKNOWN, + ) + val bypassEnabled by collectLastValue(underTest.isBypassAvailable) + kosmos.devicePostureController.verifyNoCallback() + + // Assert bypass enabled + assertThat(bypassEnabled).isTrue() + } + + // overrideFaceBypassSetting overridden to false + // isFaceEnrolledAndEnabled true + // isPostureAllowedForFaceAuth always true given DEVICE_POSTURE_UNKNOWN config + @Test + fun bypassEnabledFalse_bypassNever_unknownDevicePostureConfig() = + testScope.runTest { + // KeyguardBypassRepository#overrideFaceBypassSetting = false due to NEVER override + // Set face auth posture config to unknown + initUnderTest( + faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NEVER, + faceAuthPostureConfig = DEVICE_POSTURE_UNKNOWN, + ) + val bypassEnabled by collectLastValue(underTest.isBypassAvailable) + kosmos.devicePostureController.verifyNoCallback() + + // Assert bypass enabled + assertThat(bypassEnabled).isFalse() + } + + private fun TestScope.initUnderTest( + faceUnlockBypassOverrideConfig: Int, + faceAuthPostureConfig: Int, + ) { + kosmos.configureKeyguardBypass( + faceAuthEnrolledAndEnabled = true, + faceUnlockBypassOverrideConfig = faceUnlockBypassOverrideConfig, + faceAuthPostureConfig = faceAuthPostureConfig, + ) + underTest = kosmos.keyguardBypassRepository + runCurrent() + } + + companion object { + private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0 + private const val FACE_UNLOCK_BYPASS_ALWAYS = 1 + private const val FACE_UNLOCK_BYPASS_NEVER = 2 + } +} + +private const val faceUnlockDismissesKeyguard = Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD + +private fun TunerService.captureCallback() = + withArgCaptor<TunerService.Tunable> { + verify(this@captureCallback).addTunable(capture(), eq(faceUnlockDismissesKeyguard)) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index 020f7fa6256b..c9458125e762 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -30,6 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.data.repository.PulseExpansionRepository import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule import com.android.systemui.scene.SceneContainerFrameworkModule import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -70,11 +72,17 @@ import kotlinx.coroutines.test.runTest interface SysUITestModule { @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext + @Binds fun bindContext(testableContext: TestableContext): Context + @Binds @Application fun bindAppContext(context: Context): Context + @Binds @Application fun bindAppResources(resources: Resources): Resources + @Binds @Main fun bindMainResources(resources: Resources): Resources + @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher + @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor @Binds @@ -108,7 +116,7 @@ interface SysUITestModule { @Provides fun provideBaseShadeInteractor( sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, - sceneContainerOff: Provider<ShadeInteractorLegacyImpl> + sceneContainerOff: Provider<ShadeInteractorLegacyImpl>, ): BaseShadeInteractor { return if (SceneContainerFlag.isEnabled) { sceneContainerOn.get() @@ -125,6 +133,12 @@ interface SysUITestModule { ): SceneDataSourceDelegator { return SceneDataSourceDelegator(applicationScope, config) } + + @Provides + @SysUISingleton + fun providesPulseExpansionRepository(dumpManager: DumpManager): PulseExpansionRepository { + return PulseExpansionRepository(dumpManager) + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index 878e38594fe1..2a7e3e903737 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -20,6 +20,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.keyguard.logging.biometricUnlockLogger import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +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.kosmos.Kosmos @@ -27,17 +28,19 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.time.systemClock import kotlinx.coroutines.ExperimentalCoroutinesApi +@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.deviceEntryHapticsInteractor by Kosmos.Fixture { DeviceEntryHapticsInteractor( - deviceEntrySourceInteractor = deviceEntrySourceInteractor, - deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + biometricSettingsRepository = biometricSettingsRepository, deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor, + deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + deviceEntrySourceInteractor = deviceEntrySourceInteractor, fingerprintPropertyRepository = fingerprintPropertyRepository, - biometricSettingsRepository = biometricSettingsRepository, keyEventInteractor = keyEventInteractor, + logger = biometricUnlockLogger, powerInteractor = powerInteractor, systemClock = systemClock, - logger = biometricUnlockLogger, + dumpManager = dumpManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt index 0b9ec92af2b5..f91a044ad802 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt @@ -16,14 +16,34 @@ package com.android.systemui.deviceentry.domain.interactor +import com.android.keyguard.keyguardUpdateMonitor +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.biometrics.authController +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.statusbar.phone.dozeScrimController import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi val Kosmos.deviceEntrySourceInteractor by Kosmos.Fixture { DeviceEntrySourceInteractor( + authenticationInteractor = authenticationInteractor, + authController = authController, + alternateBouncerInteractor = alternateBouncerInteractor, + deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor, + deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + dozeScrimController = dozeScrimController, + keyguardBypassInteractor = keyguardBypassInteractor, + keyguardUpdateMonitor = keyguardUpdateMonitor, keyguardInteractor = keyguardInteractor, + sceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor, + sceneInteractor = sceneInteractor, + dumpManager = dumpManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt new file mode 100644 index 000000000000..fd882a8eed4a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.settings.userTracker + +val Kosmos.keyguardQuickAffordanceProviderClientFactory by + Kosmos.Fixture { FakeKeyguardQuickAffordanceProviderClientFactory(userTracker) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt new file mode 100644 index 000000000000..21d1a76088fa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.applicationContext +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.settings.userFileManager +import com.android.systemui.settings.userTracker + +val Kosmos.localUserSelectionManager by + Kosmos.Fixture { + KeyguardQuickAffordanceLocalUserSelectionManager( + context = applicationContext, + userFileManager = userFileManager, + userTracker = userTracker, + broadcastDispatcher = broadcastDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt new file mode 100644 index 000000000000..ec630719ee09 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.os.UserHandle +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.userTracker + +val Kosmos.remoteUserSelectionManager by + Kosmos.Fixture { + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = testScope.backgroundScope, + userTracker = userTracker, + clientFactory = keyguardQuickAffordanceProviderClientFactory, + userHandle = UserHandle.SYSTEM, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt index 9bbb34c970d0..84eea6240e97 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.statusbar.policy.devicePostureController val Kosmos.devicePostureRepository: DevicePostureRepository by - Kosmos.Fixture { FakeDevicePostureRepository() } + Kosmos.Fixture { DevicePostureRepositoryImpl(devicePostureController, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt new file mode 100644 index 000000000000..c91823cc2999 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.content.testableContext +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.tuner.tunerService +import com.android.systemui.util.mockito.withArgCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +val Kosmos.keyguardBypassRepository: KeyguardBypassRepository by Fixture { + KeyguardBypassRepository( + testableContext.resources, + biometricSettingsRepository, + devicePostureRepository, + dumpManager, + tunerService, + testDispatcher, + ) +} + +fun Kosmos.configureKeyguardBypass( + isBypassAvailable: Boolean? = null, + faceAuthEnrolledAndEnabled: Boolean = true, + faceUnlockBypassOverrideConfig: Int = 0, /* FACE_UNLOCK_BYPASS_NO_OVERRIDE */ + faceAuthPostureConfig: Int = DEVICE_POSTURE_UNKNOWN, +) { + when (isBypassAvailable) { + null -> { + biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(faceAuthEnrolledAndEnabled) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + faceUnlockBypassOverrideConfig, + ) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + faceAuthPostureConfig, + ) + } + true -> { + biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 1, /* FACE_UNLOCK_BYPASS_ALWAYS */ + ) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_UNKNOWN, + ) + } + false -> { + biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 2, /* FACE_UNLOCK_BYPASS_NEVER */ + ) + testableContext.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_CLOSED, + ) + } + } +} + +fun DevicePostureController.verifyCallback() = + withArgCaptor<DevicePostureController.Callback> { + verify(this@verifyCallback).addCallback(capture()) + } + +fun DevicePostureController.verifyNoCallback() = + verify(this@verifyNoCallback, never()).addCallback(any()) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt new file mode 100644 index 000000000000..a6d4ec5dfb2c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.content.applicationContext +import android.os.UserHandle +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.data.quickaffordance.localUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.remoteUserSelectionManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.userTracker +import org.mockito.kotlin.mock + +val Kosmos.keyguardQuickAffordanceRepository by Fixture { + KeyguardQuickAffordanceRepository( + appContext = applicationContext, + scope = testScope.backgroundScope, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, + legacySettingSyncer = mock(), + configs = setOf(), + dumpManager = dumpManager, + userHandle = UserHandle.SYSTEM, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt new file mode 100644 index 000000000000..1851774418a3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos + +val Kosmos.pulseExpansionRepository: PulseExpansionRepository by + Kosmos.Fixture { PulseExpansionRepository(dumpManager) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt new file mode 100644 index 000000000000..59ef9df70058 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.data.repository.keyguardBypassRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor + +val Kosmos.keyguardBypassInteractor by Fixture { + KeyguardBypassInteractor( + keyguardBypassRepository, + alternateBouncerInteractor, + keyguardQuickAffordanceInteractor, + pulseExpansionInteractor, + sceneInteractor, + shadeInteractor, + dumpManager, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt new file mode 100644 index 000000000000..009d17e1a329 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.app.admin.devicePolicyManager +import android.content.applicationContext +import com.android.internal.widget.lockPatternUtils +import com.android.keyguard.logging.KeyguardQuickAffordancesLogger +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.dock.dockManager +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.keyguardQuickAffordanceRepository +import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.plugins.activityStarter +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.policy.keyguardStateController +import org.mockito.kotlin.mock + +var Kosmos.keyguardQuickAffordanceInteractor by Fixture { + KeyguardQuickAffordanceInteractor( + keyguardInteractor = keyguardInteractor, + shadeInteractor = shadeInteractor, + lockPatternUtils = lockPatternUtils, + keyguardStateController = keyguardStateController, + userTracker = userTracker, + activityStarter = activityStarter, + featureFlags = featureFlagsClassic, + repository = { keyguardQuickAffordanceRepository }, + launchAnimator = dialogTransitionAnimator, + logger = mock<KeyguardQuickAffordancesLogger>(), + metricsLogger = mock<KeyguardQuickAffordancesMetricsLogger>(), + devicePolicyManager = devicePolicyManager, + dockManager = dockManager, + biometricSettingsRepository = biometricSettingsRepository, + backgroundDispatcher = testDispatcher, + appContext = applicationContext, + sceneInteractor = { sceneInteractor }, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt new file mode 100644 index 000000000000..f02e0a44ff5d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.data.repository.pulseExpansionRepository +import com.android.systemui.kosmos.Kosmos + +val Kosmos.pulseExpansionInteractor: PulseExpansionInteractor by + Kosmos.Fixture { PulseExpansionInteractor(pulseExpansionRepository, dumpManager) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 3c87106bf5aa..3ab686da1a6c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope @@ -41,6 +42,7 @@ val Kosmos.keyguardRootViewModel by Fixture { communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, + pulseExpansionInteractor = pulseExpansionInteractor, aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel, notificationShadeWindowModel = notificationShadeWindowModel, alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 522c387a0b08..5eaa198fb2a6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -50,6 +50,7 @@ import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransit import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.power.data.repository.fakePowerRepository @@ -124,6 +125,7 @@ class KosmosJavaAdapter() { val sceneBackInteractor by lazy { kosmos.sceneBackInteractor } val falsingCollector by lazy { kosmos.falsingCollector } val powerInteractor by lazy { kosmos.powerInteractor } + val pulseExpansionInteractor by lazy { kosmos.pulseExpansionInteractor } val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } val deviceEntryUdfpsInteractor by lazy { kosmos.deviceEntryUdfpsInteractor } val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt new file mode 100644 index 000000000000..d6dd867a4f35 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import com.android.systemui.camera.cameraGestureHelper +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.keyguardBypassController + +val Kosmos.cameraLauncher by + Kosmos.Fixture { + CameraLauncher(cameraGestureHelper, keyguardBypassController) { + keyguardQuickAffordanceInteractor + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt index 61a38b864c40..9c2a2be52e92 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt @@ -22,7 +22,5 @@ import com.android.systemui.statusbar.notification.data.repository.notifications import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor val Kosmos.notificationsKeyguardInteractor by Fixture { - NotificationsKeyguardInteractor( - repository = notificationsKeyguardViewStateRepository, - ) + NotificationsKeyguardInteractor(repository = notificationsKeyguardViewStateRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt new file mode 100644 index 000000000000..9bc3ae9c1c24 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.tuner + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import org.mockito.kotlin.mock + +val Kosmos.tunerService by Fixture { mock<TunerService>() } |