Loading packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90 import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState Loading Loading @@ -112,6 +114,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() { windowManager, displayStateInteractor, Optional.of(fingerprintInteractiveToAuthProvider), kosmos.biometricSettingsRepository, kosmos.keyguardTransitionInteractor, SideFpsLogger(logcatLogBuffer("SfpsLogger")) ) Loading Loading @@ -420,6 +423,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() { @Test fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() = testScope.runTest { kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) setupFingerprint(FingerprintSensorType.POWER_BUTTON) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt 0 → 100644 +189 −0 Original line number Diff line number Diff line /* * 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.ui.viewmodel import android.content.applicationContext import android.hardware.biometrics.BiometricFingerprintConstants import android.os.PowerManager import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.phone.dozeServiceHost import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi 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.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4::class) class SideFpsProgressBarViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var underTest: SideFpsProgressBarViewModel private val testScope = kosmos.testScope private lateinit var mTestableLooper: TestableLooper @Before fun setup() { mTestableLooper = TestableLooper.get(this) allowTestableLooperAsMainThread() } private suspend fun setupRestToUnlockEnabled() { mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK) overrideResource(R.bool.config_restToUnlockSupported, true) kosmos.fakeFingerprintPropertyRepository.setProperties( 1, SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON, mutableMapOf(Pair("sensor", mock())) ) kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 0.0f, transitionState = TransitionState.STARTED ) ) kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 1.0f, transitionState = TransitionState.FINISHED ) ) kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) } @Test fun whenConfigDisabled_featureIsDisabled() = testScope.runTest { overrideResource(R.bool.config_restToUnlockSupported, false) underTest = createViewModel() val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) assertThat(enabled).isFalse() } @Test fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() = testScope.runTest { overrideResource(R.bool.config_restToUnlockSupported, true) underTest = createViewModel() val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) assertThat(enabled).isFalse() kosmos.fakeFingerprintPropertyRepository.setProperties( 1, SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON, mutableMapOf(Pair("sensor", mock())) ) assertThat(enabled).isFalse() kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) runCurrent() assertThat(enabled).isTrue() } @Test fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() = testScope.runTest { setupRestToUnlockEnabled() underTest = createViewModel() kosmos.fakeKeyguardRepository.setIsDozing(false) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( AcquiredFingerprintAuthenticationStatus( BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START ) ) runCurrent() assertThat(kosmos.fakePowerRepository.lastWakeReason) .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC) } @Test fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() = testScope.runTest { setupRestToUnlockEnabled() underTest = createViewModel() kosmos.fakeKeyguardRepository.setIsDozing(true) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( AcquiredFingerprintAuthenticationStatus( BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START ) ) runCurrent() verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted() } private fun createViewModel() = SideFpsProgressBarViewModel( kosmos.applicationContext, kosmos.deviceEntryFingerprintAuthInteractor, kosmos.sideFpsSensorInteractor, kosmos.dozeServiceHost, kosmos.keyguardInteractor, kosmos.displayStateInteractor, kosmos.testDispatcher, kosmos.applicationCoroutineScope, kosmos.powerInteractor, ) } packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt +18 −2 Original line number Diff line number Diff line Loading @@ -26,20 +26,24 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.isDefaultOrientation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.SideFpsLogger import com.android.systemui.res.R import java.util.Optional import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @ExperimentalCoroutinesApi @SysUISingleton class SideFpsSensorInteractor @Inject Loading @@ -49,6 +53,7 @@ constructor( windowManager: WindowManager, displayStateInteractor: DisplayStateInteractor, fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>, biometricSettingsRepository: BiometricSettingsRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val logger: SideFpsLogger, ) { Loading Loading @@ -84,13 +89,24 @@ constructor( .map { it ?: 0L } .onEach { logger.authDurationChanged(it) } private val isSettingEnabled: Flow<Boolean> = biometricSettingsRepository.isFingerprintEnrolledAndEnabled .flatMapLatest { enabledAndEnrolled -> if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) { flowOf(false) } else { fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser } } .onEach { logger.restToUnlockSettingEnabledChanged(it) } val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) { if (!isProlongedTouchEnabledForDevice) { flowOf(false) } else { combine( isAvailable, fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser isSettingEnabled, ) { sfpsAvailable, isSettingEnabled -> sfpsAvailable && isSettingEnabled } Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -98,7 +98,7 @@ constructor( val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing val isDozing: StateFlow<Boolean> = repository.isDozing /** Receive an event for doze time tick */ val dozeTimeTick: Flow<Long> = repository.dozeTimeTick Loading packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt +53 −41 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ import android.animation.ValueAnimator import android.content.Context import android.graphics.Point import androidx.annotation.VisibleForTesting import androidx.core.animation.doOnEnd import androidx.core.animation.addListener import com.android.systemui.Flags import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor Loading @@ -30,15 +30,18 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.phone.DozeServiceHost import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -46,13 +49,14 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @ExperimentalCoroutinesApi @SysUISingleton class SideFpsProgressBarViewModel @Inject Loading @@ -63,9 +67,11 @@ constructor( // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through // DozeInteractor as DozeServiceHost already depends on DozeInteractor. private val dozeServiceHost: DozeServiceHost, private val keyguardInteractor: KeyguardInteractor, displayStateInteractor: DisplayStateInteractor, @Main private val mainDispatcher: CoroutineDispatcher, @Application private val applicationScope: CoroutineScope, private val powerInteractor: PowerInteractor, ) { private val _progress = MutableStateFlow(0.0f) private val _visible = MutableStateFlow(false) Loading Loading @@ -176,31 +182,36 @@ constructor( return@collectLatest } animatorJob = combine( sfpsSensorInteractor.authenticationDuration, fpAuthRepository.authenticationStatus, ::Pair ) .onEach { (authDuration, authStatus) -> sfpsSensorInteractor.authenticationDuration .flatMapLatest { authDuration -> _animator?.cancel() fpAuthRepository.authenticationStatus.map { authStatus -> when (authStatus) { is AcquiredFingerprintAuthenticationStatus -> { if (authStatus.fingerprintCaptureStarted) { _visible.value = true if (keyguardInteractor.isDozing.value) { dozeServiceHost.fireSideFpsAcquisitionStarted() } else { powerInteractor .wakeUpForSideFingerprintAcquisition() } _animator?.cancel() _animator = ValueAnimator.ofFloat(0.0f, 1.0f) .setDuration(authDuration) .apply { addUpdateListener { _progress.value = it.animatedValue as Float _progress.value = it.animatedValue as Float } addListener( doOnEnd { onEnd = { if (_progress.value == 0.0f) { _visible.value = false } } }, onStart = { _visible.value = true }, onCancel = { _visible.value = false } ) } _animator?.start() Loading @@ -220,6 +231,7 @@ constructor( else -> Unit } } } .flowOn(mainDispatcher) .onCompletion { _animator?.cancel() } .launchIn(applicationScope) Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90 import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState Loading Loading @@ -112,6 +114,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() { windowManager, displayStateInteractor, Optional.of(fingerprintInteractiveToAuthProvider), kosmos.biometricSettingsRepository, kosmos.keyguardTransitionInteractor, SideFpsLogger(logcatLogBuffer("SfpsLogger")) ) Loading Loading @@ -420,6 +423,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() { @Test fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() = testScope.runTest { kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) setupFingerprint(FingerprintSensorType.POWER_BUTTON) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt 0 → 100644 +189 −0 Original line number Diff line number Diff line /* * 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.ui.viewmodel import android.content.applicationContext import android.hardware.biometrics.BiometricFingerprintConstants import android.os.PowerManager import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.phone.dozeServiceHost import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi 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.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4::class) class SideFpsProgressBarViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var underTest: SideFpsProgressBarViewModel private val testScope = kosmos.testScope private lateinit var mTestableLooper: TestableLooper @Before fun setup() { mTestableLooper = TestableLooper.get(this) allowTestableLooperAsMainThread() } private suspend fun setupRestToUnlockEnabled() { mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK) overrideResource(R.bool.config_restToUnlockSupported, true) kosmos.fakeFingerprintPropertyRepository.setProperties( 1, SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON, mutableMapOf(Pair("sensor", mock())) ) kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 0.0f, transitionState = TransitionState.STARTED ) ) kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.LOCKSCREEN, to = KeyguardState.AOD, value = 1.0f, transitionState = TransitionState.FINISHED ) ) kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) } @Test fun whenConfigDisabled_featureIsDisabled() = testScope.runTest { overrideResource(R.bool.config_restToUnlockSupported, false) underTest = createViewModel() val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) assertThat(enabled).isFalse() } @Test fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() = testScope.runTest { overrideResource(R.bool.config_restToUnlockSupported, true) underTest = createViewModel() val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication) assertThat(enabled).isFalse() kosmos.fakeFingerprintPropertyRepository.setProperties( 1, SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON, mutableMapOf(Pair("sensor", mock())) ) assertThat(enabled).isFalse() kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) runCurrent() assertThat(enabled).isTrue() } @Test fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() = testScope.runTest { setupRestToUnlockEnabled() underTest = createViewModel() kosmos.fakeKeyguardRepository.setIsDozing(false) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( AcquiredFingerprintAuthenticationStatus( BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START ) ) runCurrent() assertThat(kosmos.fakePowerRepository.lastWakeReason) .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC) } @Test fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() = testScope.runTest { setupRestToUnlockEnabled() underTest = createViewModel() kosmos.fakeKeyguardRepository.setIsDozing(true) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( AcquiredFingerprintAuthenticationStatus( BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START ) ) runCurrent() verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted() } private fun createViewModel() = SideFpsProgressBarViewModel( kosmos.applicationContext, kosmos.deviceEntryFingerprintAuthInteractor, kosmos.sideFpsSensorInteractor, kosmos.dozeServiceHost, kosmos.keyguardInteractor, kosmos.displayStateInteractor, kosmos.testDispatcher, kosmos.applicationCoroutineScope, kosmos.powerInteractor, ) }
packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt +18 −2 Original line number Diff line number Diff line Loading @@ -26,20 +26,24 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.isDefaultOrientation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.SideFpsLogger import com.android.systemui.res.R import java.util.Optional import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @ExperimentalCoroutinesApi @SysUISingleton class SideFpsSensorInteractor @Inject Loading @@ -49,6 +53,7 @@ constructor( windowManager: WindowManager, displayStateInteractor: DisplayStateInteractor, fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>, biometricSettingsRepository: BiometricSettingsRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val logger: SideFpsLogger, ) { Loading Loading @@ -84,13 +89,24 @@ constructor( .map { it ?: 0L } .onEach { logger.authDurationChanged(it) } private val isSettingEnabled: Flow<Boolean> = biometricSettingsRepository.isFingerprintEnrolledAndEnabled .flatMapLatest { enabledAndEnrolled -> if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) { flowOf(false) } else { fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser } } .onEach { logger.restToUnlockSettingEnabledChanged(it) } val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) { if (!isProlongedTouchEnabledForDevice) { flowOf(false) } else { combine( isAvailable, fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser isSettingEnabled, ) { sfpsAvailable, isSettingEnabled -> sfpsAvailable && isSettingEnabled } Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -98,7 +98,7 @@ constructor( val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing val isDozing: StateFlow<Boolean> = repository.isDozing /** Receive an event for doze time tick */ val dozeTimeTick: Flow<Long> = repository.dozeTimeTick Loading
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt +53 −41 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ import android.animation.ValueAnimator import android.content.Context import android.graphics.Point import androidx.annotation.VisibleForTesting import androidx.core.animation.doOnEnd import androidx.core.animation.addListener import com.android.systemui.Flags import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor Loading @@ -30,15 +30,18 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.phone.DozeServiceHost import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -46,13 +49,14 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @ExperimentalCoroutinesApi @SysUISingleton class SideFpsProgressBarViewModel @Inject Loading @@ -63,9 +67,11 @@ constructor( // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through // DozeInteractor as DozeServiceHost already depends on DozeInteractor. private val dozeServiceHost: DozeServiceHost, private val keyguardInteractor: KeyguardInteractor, displayStateInteractor: DisplayStateInteractor, @Main private val mainDispatcher: CoroutineDispatcher, @Application private val applicationScope: CoroutineScope, private val powerInteractor: PowerInteractor, ) { private val _progress = MutableStateFlow(0.0f) private val _visible = MutableStateFlow(false) Loading Loading @@ -176,31 +182,36 @@ constructor( return@collectLatest } animatorJob = combine( sfpsSensorInteractor.authenticationDuration, fpAuthRepository.authenticationStatus, ::Pair ) .onEach { (authDuration, authStatus) -> sfpsSensorInteractor.authenticationDuration .flatMapLatest { authDuration -> _animator?.cancel() fpAuthRepository.authenticationStatus.map { authStatus -> when (authStatus) { is AcquiredFingerprintAuthenticationStatus -> { if (authStatus.fingerprintCaptureStarted) { _visible.value = true if (keyguardInteractor.isDozing.value) { dozeServiceHost.fireSideFpsAcquisitionStarted() } else { powerInteractor .wakeUpForSideFingerprintAcquisition() } _animator?.cancel() _animator = ValueAnimator.ofFloat(0.0f, 1.0f) .setDuration(authDuration) .apply { addUpdateListener { _progress.value = it.animatedValue as Float _progress.value = it.animatedValue as Float } addListener( doOnEnd { onEnd = { if (_progress.value == 0.0f) { _visible.value = false } } }, onStart = { _visible.value = true }, onCancel = { _visible.value = false } ) } _animator?.start() Loading @@ -220,6 +231,7 @@ constructor( else -> Unit } } } .flowOn(mainDispatcher) .onCompletion { _animator?.cancel() } .launchIn(applicationScope) Loading