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

Commit e9f93cc8 authored by Beverly's avatar Beverly
Browse files

AltBouncer => PrimaryBouncer on strong face auth lockout

If class 3 face auth locks out on the alternate bouncer,
then show the primary bouncer since any strong biometric
lockout will lockout all other biometrics.

Test: atest AlternateBouncerViewModelTest
Test: on a device with strong fingerprint and strong
  face: enroll fingerprint; enroll face; tap on a
  notification to show the alternate bouncer;
  fail face auth until lockout on the alternate
  bouncer; observe primary bouncer shows
Fixes: 421121945
Flag: EXEMPT bugfix
Change-Id: Icc1af84f3a8cb09d821cdb186bf8e2004ccbca35
parent d5f3da8f
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.log.TouchHandlingViewLogger
import com.android.systemui.res.R

@@ -64,9 +65,9 @@ fun AlternateBouncer(
    onHideAnimationFinished: () -> Unit,
    modifier: Modifier = Modifier,
) {

    val isVisible by
        alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle(true)
    val alternateBouncerViewModel =
        rememberViewModel("AlternateBouncerViewModel") { alternateBouncerDependencies.viewModel }
    val isVisible by alternateBouncerViewModel.isVisible.collectAsStateWithLifecycle(true)
    val visibleState = remember { MutableTransitionState(isVisible) }

    // Feeds the isVisible value to the MutableTransitionState used by AnimatedVisibility below.
@@ -98,7 +99,7 @@ fun AlternateBouncer(
                Modifier.background(color = Colors.AlternateBouncerBackgroundColor).pointerInput(
                    Unit
                ) {
                    detectTapGestures(onTap = { alternateBouncerDependencies.viewModel.onTapped() })
                    detectTapGestures(onTap = { alternateBouncerViewModel.onTapped() })
                },
        ) {
            StatusMessage(viewModel = alternateBouncerDependencies.messageAreaViewModel)
+52 −0
Original line number Diff line number Diff line
@@ -21,16 +21,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
@@ -38,11 +43,15 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.kotlin.never

@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -154,6 +163,49 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
            assertThat(registerForDismissGestures).isFalse()
        }

    @Test
    fun strongFaceAuthLockout_showPrimaryBouncer() =
        testScope.runTest {
            underTest.activateIn(this)
            setFaceAuthSensor(strength = SensorStrength.STRONG)
            runCurrent()
            kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
            runCurrent()

            verify(kosmos.statusBarKeyguardViewManager)
                .showPrimaryBouncer(anyBoolean(), anyString())
        }

    @Test
    fun weakFaceAuthLockout_doNotShowPrimaryBouncer() =
        testScope.runTest {
            underTest.activateIn(this)
            setFaceAuthSensor(strength = SensorStrength.WEAK)
            runCurrent()
            kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
            runCurrent()

            verify(kosmos.statusBarKeyguardViewManager, never())
                .showPrimaryBouncer(anyBoolean(), anyString())
        }

    @Test
    fun convenienceFaceAuthLockout_doNotShowPrimaryBouncer() =
        testScope.runTest {
            underTest.activateIn(this)
            setFaceAuthSensor(strength = SensorStrength.CONVENIENCE)
            runCurrent()
            kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
            runCurrent()

            verify(kosmos.statusBarKeyguardViewManager, never())
                .showPrimaryBouncer(anyBoolean(), anyString())
        }

    private fun setFaceAuthSensor(strength: SensorStrength) {
        kosmos.fakeFacePropertyRepository.setSensorInfo(FaceSensorInfo(id = 0, strength = strength))
    }

    private fun stepToAlternateBouncer(
        value: Float,
        state: TransitionState = TransitionState.RUNNING,
+1 −1
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ constructor(
     */
    val isFaceLockedOut: StateFlow<Boolean> = deviceEntryFaceAuthInteractor.isLockedOut

    private val isStrongFaceAuth: StateFlow<Boolean> =
    val isStrongFaceAuth: StateFlow<Boolean> =
        facePropertyRepository.sensorInfo
            .map { it?.strength == SensorStrength.STRONG }
            .stateIn(
+12 −0
Original line number Diff line number Diff line
@@ -39,7 +39,9 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.log.TouchHandlingViewLogger
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -215,6 +217,16 @@ constructor(
                }
            }
        }

        view.repeatWhenAttached {
            view.viewModel(
                traceName = "AlternateBouncerViewBinderViewModel",
                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                factory = { alternateBouncerDependencies.viewModel },
            ) {
                // no-op - currently used to activate viewModel
            }
        }
    }

    private fun optionallyAddUdfpsViews(
+28 −1
Original line number Diff line number Diff line
@@ -20,18 +20,26 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Color
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

class AlternateBouncerViewModel
@Inject
@@ -41,7 +49,9 @@ constructor(
    private val dismissCallbackRegistry: DismissCallbackRegistry,
    alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>,
    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
    deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
    deviceEntryFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor,
) : ExclusiveActivatable() {
    // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
    private val alternateBouncerScrimAlpha = .66f

@@ -64,6 +74,19 @@ constructor(
    val registerForDismissGestures: Flow<Boolean> =
        transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged()

    val strongFaceAuthLockout =
        deviceEntryBiometricsAllowedInteractor.isStrongFaceAuth.flatMapLatest { isStrongFaceAuth ->
            if (isStrongFaceAuth) {
                deviceEntryFaceAuthInteractor.isLockedOut.filter { it }.map {} // Unit
            } else {
                emptyFlow()
            }
        }

    override suspend fun onActivated() {
        coroutineScope { launch { strongFaceAuthLockout.collect { onStrongFaceAuthLockout() } } }
    }

    fun onTapped() {
        statusBarKeyguardViewManager.showPrimaryBouncer(
            /* scrimmed */ true,
@@ -80,4 +103,8 @@ constructor(
        dismissCallbackRegistry.notifyDismissCancelled()
        primaryBouncerInteractor.setDismissAction(null, null)
    }

    private fun onStrongFaceAuthLockout() {
        statusBarKeyguardViewManager.showPrimaryBouncer(true, "strongFaceAuthLockout")
    }
}
Loading