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

Commit 79675a50 authored by Chandru S's avatar Chandru S Committed by Android (Google) Code Review
Browse files

Merge changes I72b746ba,I0c319c40,Ic81a900a,I5694044a into main

* changes:
  Tapping on the lockscreen should trigger face auth
  Navigating to the compose bouncer should trigger face auth
  Fix issue where face auth doesn't run when bouncer is launched from secure camera
  Fix face auth not running as expected when flexiglass is enabled.
parents b7610715 5e77e6a4
Loading
Loading
Loading
Loading
+107 −24
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.hardware.face.FaceSensorPropertiesInternal
import android.os.CancellationSignal
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@ import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -77,6 +79,9 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -90,6 +95,7 @@ import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {

    @Captor
    private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
    private val testDispatcher = kosmos.testDispatcher
    private val testDispatcher by lazy { kosmos.testDispatcher }

    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
    private val testScope = kosmos.testScope
    private val fakeUserRepository = kosmos.fakeUserRepository
    private val fakeExecutor = kosmos.fakeExecutor
    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
    private val testScope by lazy { kosmos.testScope }
    private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
    private val fakeExecutor by lazy { kosmos.fakeExecutor }
    private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
    private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
    private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
    private lateinit var lockedOut: FlowValue<Boolean?>
    private lateinit var canFaceAuthRun: FlowValue<Boolean?>
    private lateinit var authenticated: FlowValue<Boolean?>
    private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
    private val deviceEntryFingerprintAuthRepository =
    private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
    private val deviceEntryFingerprintAuthRepository by lazy {
        kosmos.fakeDeviceEntryFingerprintAuthRepository
    private val trustRepository = kosmos.fakeTrustRepository
    private val keyguardRepository = kosmos.fakeKeyguardRepository
    private val powerInteractor = kosmos.powerInteractor
    private val keyguardInteractor = kosmos.keyguardInteractor
    private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
    private val displayStateInteractor = kosmos.displayStateInteractor
    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
    private val displayRepository = kosmos.displayRepository
    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
    }
    private val trustRepository by lazy { kosmos.fakeTrustRepository }
    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
    private val powerInteractor by lazy { kosmos.powerInteractor }
    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
    private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
    private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
    private val displayRepository by lazy { kosmos.displayRepository }
    private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
    private lateinit var featureFlags: FakeFeatureFlags

    private var wasAuthCancelled = false
@@ -180,10 +187,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
        whenever(bypassController.bypassEnabled).thenReturn(true)
        underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)

        if (!SceneContainerFlag.isEnabled) {
            mSetFlagsRule.disableFlags(
                AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
            )
        }
    }

    private fun createDeviceEntryFaceAuthRepositoryImpl(
        fmOverride: FaceManager? = faceManager,
@@ -227,6 +236,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
            powerInteractor,
            keyguardInteractor,
            alternateBouncerInteractor,
            { kosmos.sceneInteractor },
            faceDetectBuffer,
            faceAuthBuffer,
            keyguardTransitionInteractor,
@@ -546,6 +556,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
            testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) }
        }

    @Test
    @EnableSceneContainer
    fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
        testScope.runTest {
            testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
                keyguardTransitionRepository.sendTransitionStep(
                    TransitionStep(
                        KeyguardState.LOCKSCREEN,
                        KeyguardState.UNDEFINED,
                        value = 0.5f,
                        transitionState = TransitionState.RUNNING
                    ),
                    validateStep = false
                )
                runCurrent()
            }
        }

    @Test
    fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
        testScope.runTest {
@@ -594,6 +622,31 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
            assertThat(canFaceAuthRun()).isTrue()
        }

    @Test
    @EnableSceneContainer
    fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
        testScope.runTest {
            initCollectors()
            allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
            bouncerRepository.setAlternateVisible(false)

            // launch secure camera
            keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
            keyguardRepository.setKeyguardOccluded(true)
            kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
            runCurrent()
            assertThat(canFaceAuthRun()).isFalse()

            // but bouncer is shown after that.
            kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
            kosmos.sceneInteractor.setTransitionState(
                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
            )
            runCurrent()

            assertThat(canFaceAuthRun()).isTrue()
        }

    @Test
    fun authenticateDoesNotRunOnUnsupportedPosture() =
        testScope.runTest {
@@ -840,6 +893,24 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
            testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) }
        }

    @Test
    @EnableSceneContainer
    fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
        testScope.runTest {
            testGatingCheckForDetect(sceneContainerEnabled = true) {
                keyguardTransitionRepository.sendTransitionStep(
                    TransitionStep(
                        KeyguardState.LOCKSCREEN,
                        KeyguardState.UNDEFINED,
                        value = 0.5f,
                        transitionState = TransitionState.RUNNING
                    ),
                    validateStep = false
                )
                runCurrent()
            }
        }

    @Test
    fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
        testScope.runTest {
@@ -1052,10 +1123,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
        }

    private suspend fun TestScope.testGatingCheckForFaceAuth(
        sceneContainerEnabled: Boolean = false,
        gatingCheckModifier: suspend () -> Unit
    ) {
        initCollectors()
        allPreconditionsToRunFaceAuthAreTrue()
        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)

        gatingCheckModifier()
        runCurrent()
@@ -1069,7 +1141,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
        faceAuthenticateIsNotCalled()

        // flip the gating check back on.
        allPreconditionsToRunFaceAuthAreTrue()
        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
        assertThat(underTest.canRunFaceAuth.value).isTrue()

        faceAuthenticateIsCalled()
@@ -1094,10 +1166,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
    }

    private suspend fun TestScope.testGatingCheckForDetect(
        sceneContainerEnabled: Boolean = false,
        gatingCheckModifier: suspend () -> Unit
    ) {
        initCollectors()
        allPreconditionsToRunFaceAuthAreTrue()
        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)

        // This will stop face auth from running but is required to be false for detect.
        biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1218,22 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
        cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
    }

    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
        sceneContainerEnabled: Boolean = false
    ) {
        fakeExecutor.runAllReady()
        verify(faceManager, atLeastOnce())
            .addLockoutResetCallback(faceLockoutResetCallback.capture())
        trustRepository.setCurrentUserTrusted(false)
        if (sceneContainerEnabled) {
            // Keyguard is not going away
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
                validateStep = false
            )
        } else {
            keyguardRepository.setKeyguardGoingAway(false)
        }
        powerInteractor.setAwakeForTest()
        biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
        biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+21 −0
Original line number Diff line number Diff line
@@ -25,8 +25,11 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -259,6 +262,23 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
                .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
        }

    @Test
    fun triggersFaceAuthWhenLockscreenIsClicked() =
        testScope.runTest {
            collectLastValue(underTest.isMenuVisible)
            runCurrent()
            kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true

            underTest.onClick(100.0f, 100.0f)
            runCurrent()

            val runningAuthRequest =
                kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
            assertThat(runningAuthRequest?.first)
                .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
            assertThat(runningAuthRequest?.second).isEqualTo(true)
        }

    @Test
    fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
        testScope.runTest {
@@ -302,6 +322,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
                broadcastDispatcher = fakeBroadcastDispatcher,
                accessibilityManager = kosmos.accessibilityManagerWrapper,
                pulsingGestureListener = kosmos.pulsingGestureListener,
                faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
            )
        setUpState()
    }
+19 −2
Original line number Diff line number Diff line
@@ -57,7 +57,10 @@ import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.Scenes.Bouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@ constructor(
    private val powerInteractor: PowerInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val alternateBouncerInteractor: AlternateBouncerInteractor,
    private val sceneInteractor: dagger.Lazy<SceneInteractor>,
    @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
    @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,16 @@ constructor(
                biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                "isFaceAuthEnrolledAndEnabled"
            ),
            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
            Pair(
                if (SceneContainerFlag.isEnabled) {
                    keyguardTransitionInteractor
                        .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
                        .isFalse()
                } else {
                    keyguardRepository.isKeyguardGoingAway.isFalse()
                },
                "keyguardNotGoingAway"
            ),
            Pair(
                keyguardTransitionInteractor
                    .isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +410,11 @@ constructor(
                    .isFalse()
                    .or(
                        alternateBouncerInteractor.isVisible.or(
                            if (SceneContainerFlag.isEnabled) {
                                sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
                            } else {
                                keyguardInteractor.primaryBouncerShowing
                            }
                        )
                    ),
                "secureCameraNotActiveOrAnyBouncerIsShowing"
+28 −14
Original line number Diff line number Diff line
@@ -46,6 +46,9 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -90,6 +93,7 @@ constructor(
    private val powerInteractor: PowerInteractor,
    private val biometricSettingsRepository: BiometricSettingsRepository,
    private val trustManager: TrustManager,
    private val sceneInteractor: Lazy<SceneInteractor>,
    deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {

@@ -103,9 +107,7 @@ constructor(
        keyguardUpdateMonitor.setFaceAuthInteractor(this)
        observeFaceAuthStateUpdates()
        faceAuthenticationLogger.interactorStarted()
        primaryBouncerInteractor
            .get()
            .isShowing
        isBouncerVisible
            .whenItFlipsToTrue()
            .onEach {
                faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,20 +183,24 @@ constructor(
        // auth so that the switched user can unlock the device with face auth.
        userRepository.selectedUser
            .pairwise()
            .onEach { (previous, curr) ->
            .filter { (previous, curr) ->
                val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                if (wasSwitching && !isSwitching) {
                    resetLockedOutState(curr.userInfo.id)
                // User switching was in progress and is complete now.
                wasSwitching && !isSwitching
            }
            .map { (_, curr) -> curr.userInfo.id }
            .sample(isBouncerVisible, ::Pair)
            .onEach { (userId, isBouncerCurrentlyVisible) ->
                resetLockedOutState(userId)
                yield()
                runFaceAuth(
                    FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
                    // Fallback to detection if bouncer is not showing so that we can detect a
                    // face and then show the bouncer to the user if face auth can't run
                        fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
                    fallbackToDetect = !isBouncerCurrentlyVisible
                )
            }
            }
            .launchIn(applicationScope)

        facePropertyRepository.cameraInfo
@@ -210,6 +216,14 @@ constructor(
            .launchIn(applicationScope)
    }

    private val isBouncerVisible: Flow<Boolean> by lazy {
        if (SceneContainerFlag.isEnabled) {
            sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
        } else {
            primaryBouncerInteractor.get().isShowing
        }
    }

    private suspend fun resetLockedOutState(currentUserId: Int) {
        val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
        repository.setLockedOut(
+5 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -67,6 +68,7 @@ constructor(
    broadcastDispatcher: BroadcastDispatcher,
    private val accessibilityManager: AccessibilityManagerWrapper,
    private val pulsingGestureListener: PulsingGestureListener,
    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
) {
    /** Whether the long-press handling feature should be enabled. */
    val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -129,7 +131,8 @@ constructor(
        }
    }

    /** Notifies that the user has long-pressed on the lock screen.
    /**
     * Notifies that the user has long-pressed on the lock screen.
     *
     * @param isA11yAction: Whether the action was performed as an a11y action
     */
@@ -174,6 +177,7 @@ constructor(
    /** Notifies that the lockscreen has been clicked at position [x], [y]. */
    fun onClick(x: Float, y: Float) {
        pulsingGestureListener.onSingleTapUp(x, y)
        faceAuthInteractor.onNotificationPanelClicked()
    }

    /** Notifies that the lockscreen has been double clicked. */
Loading