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:
Chandru S
2023-07-25 19:28:04 -07:00
parent 9ed6c4f9a4
commit 5d9a9f736b
6 changed files with 104 additions and 15 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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