Loading packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt +5 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading Loading @@ -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) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +52 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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( Loading packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +28 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading @@ -80,4 +103,8 @@ constructor( dismissCallbackRegistry.notifyDismissCancelled() primaryBouncerInteractor.setDismissAction(null, null) } private fun onStrongFaceAuthLockout() { statusBarKeyguardViewManager.showPrimaryBouncer(true, "strongFaceAuthLockout") } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt +5 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading Loading @@ -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) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +52 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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( Loading
packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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( Loading
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +28 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading @@ -80,4 +103,8 @@ constructor( dismissCallbackRegistry.notifyDismissCancelled() primaryBouncerInteractor.setDismissAction(null, null) } private fun onStrongFaceAuthLockout() { statusBarKeyguardViewManager.showPrimaryBouncer(true, "strongFaceAuthLockout") } }