From 5d9a9f736b1a272fc630981e8acf24549ebbf313 Mon Sep 17 00:00:00 2001 From: Chandru S Date: Tue, 25 Jul 2023 19:28:04 -0700 Subject: [PATCH] Trigger face auth whenever user switching has completed. Summary of changes: - Provide API in the repository to pause/resume face auth. - To avoid race conditions between trigger and the gating check both relying on the same Flow, move this to the interactor layer Fixes: 285516490 Test: atest DeviceEntryFaceAuthRepositoryTest Test: atest KeyguardFaceAuthInteractorTest Test: verified manually, 1. Setup multiple users, unlock the device at least once after reboot with main user 2. Enroll face auth for the main user 3. Switch to second user. 4. Switch back to the main user. 5. Face auth should get triggered right after the switch. Change-Id: I69e36243ce2ebf45c8e023fdae3a2eb26dbe00d0 --- .../DeviceEntryFaceAuthRepository.kt | 29 +++++++++++--- .../NoopDeviceEntryFaceAuthRepository.kt | 3 ++ .../SystemUIKeyguardFaceAuthInteractor.kt | 26 ++++++++++++- .../DeviceEntryFaceAuthRepositoryTest.kt | 10 ++--- .../KeyguardFaceAuthInteractorTest.kt | 38 ++++++++++++++++++- .../FakeDeviceEntryFaceAuthRepository.kt | 13 +++++++ 6 files changed, 104 insertions(+), 15 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 6fd3e21b25a4d..30f8f3edfa8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -109,6 +109,18 @@ interface DeviceEntryFaceAuthRepository { /** Set whether face authentication should be locked out or not */ fun lockoutFaceAuth() + /** + * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is + * invoked. + */ + fun pauseFaceAuth() + + /** + * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to + * [authenticate] will run as long as other gating conditions don't stop it from running. + */ + fun resumeFaceAuth() + /** * Trigger face authentication. * @@ -186,6 +198,15 @@ constructor( override val isAuthRunning: StateFlow get() = _isAuthRunning + private val faceAuthPaused = MutableStateFlow(false) + override fun pauseFaceAuth() { + faceAuthPaused.value = true + } + + override fun resumeFaceAuth() { + faceAuthPaused.value = false + } + private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) @@ -329,11 +350,7 @@ constructor( "isFaceAuthenticationEnabled", tableLogBuffer ), - logAndObserve( - userRepository.userSwitchingInProgress.isFalse(), - "userSwitchingNotInProgress", - tableLogBuffer - ), + logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer), logAndObserve( keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway", @@ -454,7 +471,6 @@ constructor( } private fun handleFaceCancellationError() { - cancelNotReceivedHandlerJob?.cancel() applicationScope.launch { faceAuthRequestedWhileCancellation?.let { faceAuthLogger.launchingQueuedFaceAuthRequest(it) @@ -483,6 +499,7 @@ constructor( } private fun onFaceAuthRequestCompleted() { + cancelNotReceivedHandlerJob?.cancel() cancellationInProgress = false _isAuthRunning.value = false authCancellationSignal = null diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index 5ef9a9e0482cf..e4e6a6dae6b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -56,6 +56,9 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA get() = emptyFlow() override fun lockoutFaceAuth() = Unit + override fun pauseFaceAuth() = Unit + + override fun resumeFaceAuth() = Unit /** * Trigger face authentication. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 8f4776fa2ed3b..2a3f8520a63c9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -49,6 +50,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Encapsulates business logic related face authentication being triggered for device entry from @@ -69,6 +71,7 @@ constructor( private val faceAuthenticationLogger: FaceAuthenticationLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val userRepository: UserRepository, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList = mutableListOf() @@ -128,6 +131,23 @@ constructor( } } .launchIn(applicationScope) + + // User switching should stop face auth and then when it is complete we should trigger face + // auth so that the switched user can unlock the device with face auth. + userRepository.userSwitchingInProgress + .pairwise(false) + .onEach { (wasSwitching, isSwitching) -> + if (!wasSwitching && isSwitching) { + repository.pauseFaceAuth() + } else if (wasSwitching && !isSwitching) { + repository.resumeFaceAuth() + runFaceAuth( + FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, + fallbackToDetect = true + ) + } + } + .launchIn(applicationScope) } override fun onSwipeUpOnBouncer() { @@ -199,8 +219,10 @@ constructor( } else { faceAuthenticationStatusOverride.value = null applicationScope.launch { - faceAuthenticationLogger.authRequested(uiEvent) - repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + withContext(mainDispatcher) { + faceAuthenticationLogger.authRequested(uiEvent) + repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + } } } } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 01a6c64a6898e..85ee0e4d6ec36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -541,10 +541,8 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunIfUserIsCurrentlySwitching() = - testScope.runTest { - testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) } - } + fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() = + testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } } @Test fun authenticateDoesNotRunIfKeyguardIsNotShowing() = @@ -840,7 +838,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { @Test fun detectDoesNotRunWhenUserSwitchingInProgress() = - testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } } + testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } } @Test fun detectDoesNotRunWhenKeyguardGoingAway() = @@ -1130,7 +1128,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .addLockoutResetCallback(faceLockoutResetCallback.capture()) biometricSettingsRepository.setFaceEnrolled(true) biometricSettingsRepository.setIsFaceAuthEnabled(true) - fakeUserRepository.setUserSwitching(false) + underTest.resumeFaceAuth() trustRepository.setCurrentUserTrusted(false) keyguardRepository.setKeyguardGoingAway(false) keyguardRepository.setWakefulnessModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 8636dd8df3b03..93f208ee14f5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -74,6 +75,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository + private lateinit var fakeUserRepository: FakeUserRepository private lateinit var fakeDeviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository @@ -98,6 +100,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { .keyguardTransitionInteractor fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + fakeUserRepository = FakeUserRepository() underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -131,7 +134,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { featureFlags, FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")), keyguardUpdateMonitor, - fakeDeviceEntryFingerprintAuthRepository + fakeDeviceEntryFingerprintAuthRepository, + fakeUserRepository, ) } @@ -211,6 +215,38 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { ) } + @Test + fun faceAuthIsPausedWhenUserSwitchingIsInProgress() = + testScope.runTest { + underTest.start() + + fakeUserRepository.setUserSwitching(false) + runCurrent() + fakeUserRepository.setUserSwitching(true) + runCurrent() + + assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() + } + + @Test + fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() = + testScope.runTest { + underTest.start() + + // previously running + fakeUserRepository.setUserSwitching(true) + runCurrent() + fakeUserRepository.setUserSwitching(false) + runCurrent() + + assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value!!.first) + .isEqualTo(FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING) + assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(true) + } + @Test fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() = testScope.runTest { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index f4c2db1b944e0..1e1dc4fc59b17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -61,6 +61,19 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { _wasDisabled = true } + private val faceAuthPaused = MutableStateFlow(false) + override fun pauseFaceAuth() { + faceAuthPaused.value = true + } + + override fun resumeFaceAuth() { + faceAuthPaused.value = false + } + + fun isFaceAuthPaused(): Boolean { + return faceAuthPaused.value + } + override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { _runningAuthRequest.value = uiEvent to fallbackToDetection _isAuthRunning.value = true