From 33ddd9ab1789643fc9696f109987faa24e6f3774 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Wed, 17 May 2023 16:02:01 -0700 Subject: [PATCH] [flexiglass] Bouncer throttling - composables. Compose rendering logic for throttling input entry on the bouncer when the user enters wrong input too many times. Bug: 280877228 Test: unit tests Test: manually verified in PIN, pattern, and password that entering the wrong input 5, 10, 15, or any number above 15, times shows the throttling dialog. Test: manually verified that the throttling dialog cannot be dismissed without touching its "Ok" button (tapping outside or hitting back don't dismiss it) Test: manually verified that input on the bouncer is disabled as the message is showing the countdown for 30 seconds. Test: manually verified that after the 30 second countdown, the input is enabled again and entering the correct input unlocks Flexiglass. Change-Id: I7f5b8b7e572c4fe00f3315ccb957f036ed9e8d29 --- .../scene/ui/composable/SceneModule.kt | 4 + .../bouncer/ui/composable/BouncerScene.kt | 46 ++++++++++- .../bouncer/ui/composable/PasswordBouncer.kt | 2 + .../bouncer/ui/composable/PatternBouncer.kt | 78 ++++++++++++------- .../bouncer/ui/composable/PinBouncer.kt | 29 ++++--- .../bouncer/ui/viewmodel/BouncerViewModel.kt | 55 ++++++++----- .../ui/viewmodel/BouncerViewModelTest.kt | 11 ++- .../viewmodel/PasswordBouncerViewModelTest.kt | 10 +-- .../viewmodel/PatternBouncerViewModelTest.kt | 8 +- .../ui/viewmodel/PinBouncerViewModelTest.kt | 14 ++-- 10 files changed, 174 insertions(+), 83 deletions(-) diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt index 954bad56bcc29..d3643747ad918 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.composable +import android.content.Context import com.android.systemui.bouncer.ui.composable.BouncerScene import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.dagger.SysUISingleton @@ -28,6 +29,7 @@ import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.shade.ui.composable.ShadeScene import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.phone.SystemUIDialog import dagger.Module import dagger.Provides import javax.inject.Named @@ -57,6 +59,7 @@ object SceneModule { @SysUISingleton @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun bouncerScene( + @Application context: Context, viewModelFactory: BouncerViewModel.Factory, ): BouncerScene { return BouncerScene( @@ -64,6 +67,7 @@ object SceneModule { viewModelFactory.create( containerName = SceneContainerNames.SYSTEM_UI_DEFAULT, ), + dialogFactory = { SystemUIDialog(context) }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 3c74ef5adfeb9..240bace21a1ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -14,9 +14,16 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.android.systemui.bouncer.ui.composable +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -26,15 +33,20 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -51,6 +63,7 @@ import kotlinx.coroutines.flow.asStateFlow /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ class BouncerScene( private val viewModel: BouncerViewModel, + private val dialogFactory: () -> AlertDialog, ) : ComposableScene { override val key = SceneKey.Bouncer @@ -68,16 +81,19 @@ class BouncerScene( override fun Content( containerName: String, modifier: Modifier, - ) = BouncerScene(viewModel, modifier) + ) = BouncerScene(viewModel, dialogFactory, modifier) } @Composable private fun BouncerScene( viewModel: BouncerViewModel, + dialogFactory: () -> AlertDialog, modifier: Modifier = Modifier, ) { - val message: String by viewModel.message.collectAsState() + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -88,9 +104,10 @@ private fun BouncerScene( Crossfade( targetState = message, label = "Bouncer message", - ) { + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> Text( - text = it, + text = message.text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyLarge, ) @@ -132,5 +149,26 @@ private fun BouncerScene( style = MaterialTheme.typography.bodyMedium, ) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 4e85621e9e233..01346c7170dae 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -53,6 +53,7 @@ internal fun PasswordBouncer( ) { val focusRequester = remember { FocusRequester() } val password: String by viewModel.password.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() LaunchedEffect(Unit) { // When the UI comes up, request focus on the TextField to bring up the software keyboard. @@ -71,6 +72,7 @@ internal fun PasswordBouncer( TextField( value = password, onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, visualTransformation = PasswordVisualTransformation(), singleLine = true, textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 3afd33f4c90c5..e20833d7db173 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.unit.dp import com.android.internal.R import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt @@ -82,6 +83,8 @@ internal fun PatternBouncer( val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() // The dots selected so far, if the user is currently dragging. val selectedDots: List by viewModel.selectedDots.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } @@ -96,16 +99,24 @@ internal fun PatternBouncer( val view = LocalView.current // When the current dot is changed, we need to update our animations. - LaunchedEffect(currentDot) { + LaunchedEffect(currentDot, isAnimationEnabled) { view.performHapticFeedback( HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, ) - // Make sure that the current dot is scaled up while the other dots are scaled back down. + if (!isAnimationEnabled) { + return@LaunchedEffect + } + + // Make sure that the current dot is scaled up while the other dots are scaled back + // down. dotScalingAnimatables.entries.forEach { (dot, animatable) -> val isSelected = dot == currentDot - launch { + // Launch using the longer-lived scope because we want these animations to proceed to + // completion even if the LaunchedEffect is canceled because its key objects have + // changed. + scope.launch { animatable.animateTo(if (isSelected) 2f else 1f) if (isSelected) { animatable.animateTo(1f) @@ -116,14 +127,18 @@ internal fun PatternBouncer( selectedDots.forEach { dot -> lineFadeOutAnimatables[dot]?.let { line -> if (!line.isRunning) { + // Launch using the longer-lived scope because we want these animations to + // proceed to completion even if the LaunchedEffect is canceled because its key + // objects have changed. scope.launch { if (dot == currentDot) { - // Reset the fade-out animation for the current dot. When the current - // dot is switched, this entire code block runs again for the newly - // selected dot. + // Reset the fade-out animation for the current dot. When the + // current dot is switched, this entire code block runs again for + // the newly selected dot. line.snapTo(1f) } else { - // For all non-current dots, make sure that the lines are fading out. + // For all non-current dots, make sure that the lines are fading + // out. line.animateTo( targetValue = 0f, animationSpec = @@ -148,27 +163,34 @@ internal fun PatternBouncer( // when it leaves the bounds of the dot grid. .clipToBounds() .onSizeChanged { containerSize = it } - .pointerInput(Unit) { - detectDragGestures( - onDragStart = { start -> - inputPosition = start - viewModel.onDragStart() - }, - onDragEnd = { - inputPosition = null - lineFadeOutAnimatables.values.forEach { animatable -> - scope.launch { animatable.animateTo(1f) } - } - viewModel.onDragEnd() - }, - ) { change, _ -> - inputPosition = change.position - viewModel.onDrag( - xPx = change.position.x, - yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, - ) + .thenIf(isInputEnabled) { + Modifier.pointerInput(Unit) { + detectDragGestures( + onDragStart = { start -> + inputPosition = start + viewModel.onDragStart() + }, + onDragEnd = { + inputPosition = null + if (isAnimationEnabled) { + lineFadeOutAnimatables.values.forEach { animatable -> + // Launch using the longer-lived scope because we want these + // animations to proceed to completion even if the surrounding + // scope is canceled. + scope.launch { animatable.animateTo(1f) } + } + } + viewModel.onDragEnd() + }, + ) { change, _ -> + inputPosition = change.position + viewModel.onDrag( + xPx = change.position.x, + yPx = change.position.y, + containerSizePx = containerSize.width, + verticalOffsetPx = verticalOffset, + ) + } } } ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 9c210c225ab39..cbd7b8806a77b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -63,6 +63,7 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.compose.modifiers.thenIf import kotlin.math.max @Composable @@ -75,6 +76,7 @@ internal fun PinBouncer( // The length of the PIN input received so far, so we know how many dots to render. val pinLength: Pair by viewModel.pinLengths.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -116,6 +118,7 @@ internal fun PinBouncer( val digit = index + 1 PinButton( onClicked = { viewModel.onPinButtonClicked(digit) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(digit, contentColor) } @@ -124,6 +127,7 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onBackspaceButtonClicked() }, onLongPressed = { viewModel.onBackspaceButtonLongPressed() }, + isEnabled = isInputEnabled, isHighlighted = true, ) { contentColor -> PinIcon( @@ -138,6 +142,7 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onPinButtonClicked(0) }, + isEnabled = isInputEnabled, ) { contentColor -> PinDigit(0, contentColor) } @@ -145,6 +150,7 @@ internal fun PinBouncer( PinButton( onClicked = { viewModel.onAuthenticateButtonClicked() }, isHighlighted = true, + isEnabled = isInputEnabled, ) { contentColor -> PinIcon( Icon.Resource( @@ -187,6 +193,7 @@ private fun PinIcon( @Composable private fun PinButton( onClicked: () -> Unit, + isEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, isHighlighted: Boolean = false, @@ -228,16 +235,18 @@ private fun PinButton( cornerRadius = CornerRadius(cornerRadius.toPx()), ) } - .pointerInput(Unit) { - detectTapGestures( - onPress = { - isPressed = true - tryAwaitRelease() - isPressed = false - }, - onTap = { onClicked() }, - onLongPress = onLongPressed?.let { { onLongPressed() } }, - ) + .thenIf(isEnabled) { + Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + isPressed = true + tryAwaitRelease() + isPressed = false + }, + onTap = { onClicked() }, + onLongPress = onLongPressed?.let { { onLongPressed() } }, + ) + } }, ) { content(contentColor) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 02991bd47c6e8..984d9ab1c1be2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,7 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,21 +47,6 @@ constructor( ) { private val interactor: BouncerInteractor = interactorFactory.create(containerName) - /** - * Whether updates to the message should be cross-animated from one message to another. - * - * If `false`, no animation should be applied, the message text should just be replaced - * instantly. - */ - val isMessageUpdateAnimationsEnabled: StateFlow = - interactor.throttling - .map { it == null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null, - ) - private val isInputEnabled: StateFlow = interactor.throttling .map { it == null } @@ -104,13 +91,21 @@ constructor( ) /** The user-facing message to show in the bouncer. */ - val message: StateFlow = - interactor.message - .map { it ?: "" } + val message: StateFlow = + combine( + interactor.message, + interactor.throttling, + ) { message, throttling -> + toMessageViewModel(message, throttling) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.message.value ?: "", + initialValue = + toMessageViewModel( + message = interactor.message.value, + throttling = interactor.throttling.value, + ), ) private val _throttlingDialogMessage = MutableStateFlow(null) @@ -177,6 +172,28 @@ constructor( } } + private fun toMessageViewModel( + message: String?, + throttling: AuthenticationThrottledModel?, + ): MessageViewModel { + return MessageViewModel( + text = message ?: "", + isUpdateAnimated = throttling == null, + ) + } + + data class MessageViewModel( + val text: String, + + /** + * Whether updates to the message should be cross-animated from one message to another. + * + * If `false`, no animation should be applied, the message text should just be replaced + * instantly. + */ + val isUpdateAnimated: Boolean, + ) + @AssistedFactory interface Factory { fun create( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index b942ccbb51f32..e8c946cdd59d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -93,22 +93,21 @@ class BouncerViewModelTest : SysuiTestCase() { } @Test - fun isMessageUpdateAnimationsEnabled() = + fun message() = testScope.runTest { - val isMessageUpdateAnimationsEnabled by - collectLastValue(underTest.isMessageUpdateAnimationsEnabled) + val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234)) - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() repeat(BouncerInteractor.THROTTLE_EVERY) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(isMessageUpdateAnimationsEnabled).isFalse() + assertThat(message?.isUpdateAnimated).isFalse() throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) } - assertThat(isMessageUpdateAnimationsEnabled).isTrue() + assertThat(message?.isUpdateAnimated).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index b7b90de3b54a0..f436aa309ac50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -85,7 +85,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -109,7 +109,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(password).isEqualTo("password") assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -156,7 +156,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -179,13 +179,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("wrong") underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") - assertThat(message).isEqualTo(WRONG_PASSWORD) + assertThat(message?.text).isEqualTo(WRONG_PASSWORD) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Enter the correct password: underTest.onPasswordInputChanged("password") - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateKeyPressed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index b588ba2b2574d..d7d7154705edc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -89,7 +89,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PATTERN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN) assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -115,7 +115,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(isUnlocked).isFalse() @@ -202,7 +202,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -235,7 +235,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() - assertThat(message).isEqualTo(WRONG_PATTERN) + assertThat(message?.text).isEqualTo(WRONG_PATTERN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 83f9687d7ac53..3bdaf05908888 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -94,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onShown() - assertThat(message).isEqualTo(ENTER_YOUR_PIN) + assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -116,7 +116,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(1) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(0 to 1) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -140,7 +140,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onBackspaceButtonClicked() - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -170,7 +170,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS) } - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() assertThat(pinLengths).isEqualTo(1 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -220,7 +220,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(pinLengths).isEqualTo(0 to 0) - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @@ -244,7 +244,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(4) underTest.onPinButtonClicked(5) // PIN is now wrong! underTest.onAuthenticateButtonClicked() - assertThat(message).isEqualTo(WRONG_PIN) + assertThat(message?.text).isEqualTo(WRONG_PIN) assertThat(pinLengths).isEqualTo(0 to 0) assertThat(isUnlocked).isFalse() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -254,7 +254,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onPinButtonClicked(2) underTest.onPinButtonClicked(3) underTest.onPinButtonClicked(4) - assertThat(message).isEmpty() + assertThat(message?.text).isEmpty() underTest.onAuthenticateButtonClicked()