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
This commit is contained in:
@@ -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<Boolean>
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<FaceAuthenticationListener> = 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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user