Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5d9a9f73 authored by Chandru S's avatar Chandru S
Browse files

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
parent 9ed6c4f9
Loading
Loading
Loading
Loading
+23 −6
Original line number Diff line number Diff line
@@ -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
+3 −0
Original line number Diff line number Diff line
@@ -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.
+24 −2
Original line number Diff line number Diff line
@@ -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,10 +219,12 @@ constructor(
            } else {
                faceAuthenticationStatusOverride.value = null
                applicationScope.launch {
                    withContext(mainDispatcher) {
                        faceAuthenticationLogger.authRequested(uiEvent)
                        repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
                    }
                }
            }
        } else {
            faceAuthenticationLogger.ignoredFaceAuthTrigger(
                uiEvent,
+4 −6
Original line number Diff line number Diff line
@@ -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(
+37 −1
Original line number Diff line number Diff line
@@ -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 {
Loading