Loading packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +100 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.content.DialogInterface import androidx.annotation.VisibleForTesting import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween Loading @@ -40,6 +42,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width Loading @@ -57,6 +60,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue Loading @@ -80,6 +84,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp Loading Loading @@ -109,6 +114,8 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.fold.ui.composable.foldPosture import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.transitions.BOUNCER_INITIAL_TRANSLATION import kotlin.math.abs import kotlin.math.max import kotlin.math.pow Loading @@ -117,7 +124,7 @@ import platform.test.motion.compose.values.MotionTestValues import platform.test.motion.compose.values.motionTestValues @Composable fun BouncerContent( fun ContentScope.BouncerContent( viewModel: BouncerOverlayContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, Loading @@ -125,12 +132,100 @@ fun BouncerContent( val isOneHandedModeSupported by viewModel.isOneHandedModeSupported.collectAsStateWithLifecycle() val layout = calculateLayout(isOneHandedModeSupported = isOneHandedModeSupported) BouncerContent(layout, viewModel, dialogFactory, modifier) fun isDraggingToBouncer(): Boolean { val currentTransition = layoutState.currentTransition return currentTransition != null && currentTransition.isTransitioning(to = Overlays.Bouncer) && currentTransition.gestureContext != null } // Custom handle the BouncerContent toBouncer transition here. // fromBouncer transitions are handled by the Scene transitions. // Give an extra delay for showing BouncerContent if face auth or active unlock may run. // This gives passive auth methods an opportunity to succeed before showing bouncer contents. val appearAnimationInterpolator = FastOutSlowInEasing val appearAnimationDuration = 250 var appearAnimationDelay: Int by remember { mutableIntStateOf(0) } var startAppearAnimation: Boolean by remember { mutableStateOf(false) } val animatedAlpha: Float by animateFloatAsState( animationSpec = tween( durationMillis = appearAnimationDuration, delayMillis = appearAnimationDelay, easing = appearAnimationInterpolator, ), targetValue = if (startAppearAnimation) { 1f } else { // init alpha to 0f before anim begins 0f }, label = "alpha", ) val animatedOffsetY by animateDpAsState( animationSpec = tween( durationMillis = appearAnimationDuration, delayMillis = appearAnimationDelay, easing = appearAnimationInterpolator, ), targetValue = if (startAppearAnimation) { 0.dp } else { // init to BOUNCER_INITIAL_TRANSLATION before anim begins BOUNCER_INITIAL_TRANSLATION }, label = "offsetY", ) LaunchedEffect(Unit) { appearAnimationDelay = BOUNCER_CONTENTS_PASSIVE_AUTH_DELAY.takeIf { viewModel.shouldDelayBouncerContent() } ?: 0 startAppearAnimation = true } BouncerContent( layout, viewModel, dialogFactory, modifier = modifier .offset { val yOffset = if (isDraggingToBouncer()) { ((1 - appearAnimationInterpolator.transform( layoutState.currentTransition!!.progress )) * BOUNCER_INITIAL_TRANSLATION) .toPx() } else { animatedOffsetY.value } IntOffset(x = 0, y = yOffset.toInt()) } .graphicsLayer { alpha = if (isDraggingToBouncer()) { appearAnimationInterpolator.transform( layoutState.currentTransition!!.progress ) } else { animatedAlpha } }, ) } @Composable @VisibleForTesting fun BouncerContent( fun ContentScope.BouncerContent( layout: BouncerOverlayLayout, viewModel: BouncerOverlayContentViewModel, dialogFactory: BouncerDialogFactory, Loading Loading @@ -928,3 +1023,5 @@ private val SceneTransitions = transitions { object BouncerMotionTestKeys { val swapAnimationEnd = MotionTestValueKey<Boolean>("swapAnimationEnd") } private const val BOUNCER_CONTENTS_PASSIVE_AUTH_DELAY = 500 packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +5 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransi import com.android.systemui.scene.ui.composable.transitions.dreamToCommunalTransition import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition import com.android.systemui.scene.ui.composable.transitions.fromBouncerTransition import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition Loading Loading @@ -189,16 +190,19 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder { // Overlay transitions to(Overlays.Bouncer) { toBouncerTransition() } from(Overlays.Bouncer) { fromBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() } from(Scenes.Dream, to = Overlays.Bouncer) { dreamToBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Dream) { fromBouncerTransition() } from(Scenes.Lockscreen, to = Overlays.Bouncer) { lockscreenToBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Lockscreen) { fromBouncerTransition() } from( Scenes.Lockscreen, to = Overlays.Bouncer, key = TransitionKey.PredictiveBack, reversePreview = { bouncerToLockscreenPreview() }, ) { lockscreenToBouncerTransition() fromBouncerTransition() } from(Scenes.Communal, to = Overlays.Bouncer) { communalToBouncerTransition() } to( Loading packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerTransition.kt 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.systemui.bouncer.ui.composable.Bouncer val BOUNCER_INITIAL_TRANSLATION = 300.dp fun TransitionBuilder.fromBouncerTransition() { spec = tween(durationMillis = 500) distance = UserActionDistance { fromContent, _, _ -> val fromContentSize = checkNotNull(fromContent.targetSize()) fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION } translate(Bouncer.Elements.Content, y = BOUNCER_INITIAL_TRANSLATION) fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) } fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) } } packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt +3 −6 Original line number Diff line number Diff line Loading @@ -17,23 +17,20 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.systemui.bouncer.ui.composable.Bouncer const val TO_BOUNCER_FADE_FRACTION = 0.5f private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f fun TransitionBuilder.toBouncerTransition() { spec = tween(durationMillis = 500) spec = tween(durationMillis = 250) distance = UserActionDistance { fromContent, _, _ -> val fromContentSize = checkNotNull(fromContent.targetSize()) fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION } translate(Bouncer.Elements.Content, y = 300.dp) fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) } fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) } fade(Bouncer.Elements.Background) } packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest Loading Loading @@ -89,6 +90,46 @@ class BouncerInteractorTest : SysuiTestCase() { overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN) } @Test fun simAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_false() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Sim ) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isFalse() } @Test fun noPassiveAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_false() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = false kosmos.fakeTrustRepository.setCurrentUserActiveUnlockAvailable(false) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isFalse() } @Test fun canRunFaceAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_true() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isTrue() } @Test fun isCurrentUserActiveUnlockRunning_passiveAuthMaySucceedBeforeFullyShowingBouncer_true() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeTrustRepository.setCurrentUserActiveUnlockAvailable(true) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isTrue() } @Test fun pinAuthMethod_sim_skipsAuthentication() = testScope.runTest { Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +100 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.content.DialogInterface import androidx.annotation.VisibleForTesting import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween Loading @@ -40,6 +42,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width Loading @@ -57,6 +60,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue Loading @@ -80,6 +84,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp Loading Loading @@ -109,6 +114,8 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.fold.ui.composable.foldPosture import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.transitions.BOUNCER_INITIAL_TRANSLATION import kotlin.math.abs import kotlin.math.max import kotlin.math.pow Loading @@ -117,7 +124,7 @@ import platform.test.motion.compose.values.MotionTestValues import platform.test.motion.compose.values.motionTestValues @Composable fun BouncerContent( fun ContentScope.BouncerContent( viewModel: BouncerOverlayContentViewModel, dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, Loading @@ -125,12 +132,100 @@ fun BouncerContent( val isOneHandedModeSupported by viewModel.isOneHandedModeSupported.collectAsStateWithLifecycle() val layout = calculateLayout(isOneHandedModeSupported = isOneHandedModeSupported) BouncerContent(layout, viewModel, dialogFactory, modifier) fun isDraggingToBouncer(): Boolean { val currentTransition = layoutState.currentTransition return currentTransition != null && currentTransition.isTransitioning(to = Overlays.Bouncer) && currentTransition.gestureContext != null } // Custom handle the BouncerContent toBouncer transition here. // fromBouncer transitions are handled by the Scene transitions. // Give an extra delay for showing BouncerContent if face auth or active unlock may run. // This gives passive auth methods an opportunity to succeed before showing bouncer contents. val appearAnimationInterpolator = FastOutSlowInEasing val appearAnimationDuration = 250 var appearAnimationDelay: Int by remember { mutableIntStateOf(0) } var startAppearAnimation: Boolean by remember { mutableStateOf(false) } val animatedAlpha: Float by animateFloatAsState( animationSpec = tween( durationMillis = appearAnimationDuration, delayMillis = appearAnimationDelay, easing = appearAnimationInterpolator, ), targetValue = if (startAppearAnimation) { 1f } else { // init alpha to 0f before anim begins 0f }, label = "alpha", ) val animatedOffsetY by animateDpAsState( animationSpec = tween( durationMillis = appearAnimationDuration, delayMillis = appearAnimationDelay, easing = appearAnimationInterpolator, ), targetValue = if (startAppearAnimation) { 0.dp } else { // init to BOUNCER_INITIAL_TRANSLATION before anim begins BOUNCER_INITIAL_TRANSLATION }, label = "offsetY", ) LaunchedEffect(Unit) { appearAnimationDelay = BOUNCER_CONTENTS_PASSIVE_AUTH_DELAY.takeIf { viewModel.shouldDelayBouncerContent() } ?: 0 startAppearAnimation = true } BouncerContent( layout, viewModel, dialogFactory, modifier = modifier .offset { val yOffset = if (isDraggingToBouncer()) { ((1 - appearAnimationInterpolator.transform( layoutState.currentTransition!!.progress )) * BOUNCER_INITIAL_TRANSLATION) .toPx() } else { animatedOffsetY.value } IntOffset(x = 0, y = yOffset.toInt()) } .graphicsLayer { alpha = if (isDraggingToBouncer()) { appearAnimationInterpolator.transform( layoutState.currentTransition!!.progress ) } else { animatedAlpha } }, ) } @Composable @VisibleForTesting fun BouncerContent( fun ContentScope.BouncerContent( layout: BouncerOverlayLayout, viewModel: BouncerOverlayContentViewModel, dialogFactory: BouncerDialogFactory, Loading Loading @@ -928,3 +1023,5 @@ private val SceneTransitions = transitions { object BouncerMotionTestKeys { val swapAnimationEnd = MotionTestValueKey<Boolean>("swapAnimationEnd") } private const val BOUNCER_CONTENTS_PASSIVE_AUTH_DELAY = 500
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +5 −1 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransi import com.android.systemui.scene.ui.composable.transitions.dreamToCommunalTransition import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition import com.android.systemui.scene.ui.composable.transitions.fromBouncerTransition import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition Loading Loading @@ -189,16 +190,19 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder { // Overlay transitions to(Overlays.Bouncer) { toBouncerTransition() } from(Overlays.Bouncer) { fromBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() } from(Scenes.Dream, to = Overlays.Bouncer) { dreamToBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Dream) { fromBouncerTransition() } from(Scenes.Lockscreen, to = Overlays.Bouncer) { lockscreenToBouncerTransition() } from(Overlays.Bouncer, to = Scenes.Lockscreen) { fromBouncerTransition() } from( Scenes.Lockscreen, to = Overlays.Bouncer, key = TransitionKey.PredictiveBack, reversePreview = { bouncerToLockscreenPreview() }, ) { lockscreenToBouncerTransition() fromBouncerTransition() } from(Scenes.Communal, to = Overlays.Bouncer) { communalToBouncerTransition() } to( Loading
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerTransition.kt 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.systemui.bouncer.ui.composable.Bouncer val BOUNCER_INITIAL_TRANSLATION = 300.dp fun TransitionBuilder.fromBouncerTransition() { spec = tween(durationMillis = 500) distance = UserActionDistance { fromContent, _, _ -> val fromContentSize = checkNotNull(fromContent.targetSize()) fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION } translate(Bouncer.Elements.Content, y = BOUNCER_INITIAL_TRANSLATION) fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) } fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) } }
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt +3 −6 Original line number Diff line number Diff line Loading @@ -17,23 +17,20 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.systemui.bouncer.ui.composable.Bouncer const val TO_BOUNCER_FADE_FRACTION = 0.5f private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f fun TransitionBuilder.toBouncerTransition() { spec = tween(durationMillis = 500) spec = tween(durationMillis = 250) distance = UserActionDistance { fromContent, _, _ -> val fromContentSize = checkNotNull(fromContent.targetSize()) fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION } translate(Bouncer.Elements.Content, y = 300.dp) fractionRange(end = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Background) } fractionRange(start = TO_BOUNCER_FADE_FRACTION) { fade(Bouncer.Elements.Content) } fade(Bouncer.Elements.Background) }
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest Loading Loading @@ -89,6 +90,46 @@ class BouncerInteractorTest : SysuiTestCase() { overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN) } @Test fun simAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_false() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Sim ) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isFalse() } @Test fun noPassiveAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_false() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = false kosmos.fakeTrustRepository.setCurrentUserActiveUnlockAvailable(false) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isFalse() } @Test fun canRunFaceAuth_passiveAuthMaySucceedBeforeFullyShowingBouncer_true() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isTrue() } @Test fun isCurrentUserActiveUnlockRunning_passiveAuthMaySucceedBeforeFullyShowingBouncer_true() = kosmos.runTest { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.fakeTrustRepository.setCurrentUserActiveUnlockAvailable(true) assertThat(underTest.passiveAuthMaySucceedBeforeFullyShowingBouncer()).isTrue() } @Test fun pinAuthMethod_sim_skipsAuthentication() = testScope.runTest { Loading