Merge changes from topics "naming", "pinlength" into udc-qpr-dev
* changes: Make sure pins up to the max length of 16 digits are supported. Fix naming of Pin per style guide https://developer.android.com/kotlin/style-guide#naming_2 [flexiglass] Pin display polish.
This commit is contained in:
committed by
Android (Google) Code Review
commit
cab720e5b9
@@ -59,5 +59,20 @@ object Easings {
|
||||
/** The linear interpolator. */
|
||||
val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR)
|
||||
|
||||
/** The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN. */
|
||||
val Legacy = fromInterpolator(InterpolatorsAndroidX.LEGACY)
|
||||
|
||||
/**
|
||||
* The default legacy accelerating interpolator as defined in Material 1. Also known as
|
||||
* FAST_OUT_LINEAR_IN.
|
||||
*/
|
||||
val LegacyAccelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_ACCELERATE)
|
||||
|
||||
/**
|
||||
* T The default legacy decelerating interpolator as defined in Material 1. Also known as
|
||||
* LINEAR_OUT_SLOW_IN.
|
||||
*/
|
||||
val LegacyDecelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_DECELERATE)
|
||||
|
||||
private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) }
|
||||
}
|
||||
|
||||
@@ -19,23 +19,19 @@
|
||||
package com.android.systemui.bouncer.ui.composable
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.AnimationSpec
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.Transition
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -43,35 +39,40 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.Easings
|
||||
import com.android.compose.grid.VerticalGrid
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
|
||||
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
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlinx.coroutines.async
|
||||
@@ -86,8 +87,6 @@ internal fun PinBouncer(
|
||||
// Report that the UI is shown to let the view-model run some logic.
|
||||
LaunchedEffect(Unit) { viewModel.onShown() }
|
||||
|
||||
// The length of the PIN input received so far, so we know how many dots to render.
|
||||
val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
|
||||
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
|
||||
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
|
||||
|
||||
@@ -103,30 +102,7 @@ internal fun PinBouncer(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.heightIn(min = 16.dp).animateContentSize(),
|
||||
) {
|
||||
// TODO(b/281871687): add support for dot shapes.
|
||||
val (previousPinLength, currentPinLength) = pinLength
|
||||
val dotCount = max(previousPinLength, currentPinLength) + 1
|
||||
repeat(dotCount) { index ->
|
||||
AnimatedVisibility(
|
||||
visible = index < currentPinLength,
|
||||
enter = fadeIn() + scaleIn() + slideInHorizontally(),
|
||||
exit = fadeOut() + scaleOut() + slideOutHorizontally(),
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.size(16.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
CircleShape,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
PinInputDisplay(viewModel)
|
||||
|
||||
Spacer(Modifier.height(100.dp))
|
||||
|
||||
@@ -186,6 +162,148 @@ internal fun PinBouncer(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinInputDisplay(viewModel: PinBouncerViewModel) {
|
||||
val currentPinEntries: List<EnteredKey> by viewModel.pinEntries.collectAsState()
|
||||
|
||||
// visiblePinEntries keeps pins removed from currentPinEntries in the composition until their
|
||||
// disappear-animation completed. The list is sorted by the natural ordering of EnteredKey,
|
||||
// which is guaranteed to produce the original edit order, since the model only modifies entries
|
||||
// at the end.
|
||||
val visiblePinEntries = remember { SnapshotStateList<EnteredKey>() }
|
||||
currentPinEntries.forEach {
|
||||
val index = visiblePinEntries.binarySearch(it)
|
||||
if (index < 0) {
|
||||
val insertionPoint = -(index + 1)
|
||||
visiblePinEntries.add(insertionPoint, it)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.heightIn(min = entryShapeSize)
|
||||
// Pins overflowing horizontally should still be shown as scrolling.
|
||||
.wrapContentSize(unbounded = true),
|
||||
) {
|
||||
visiblePinEntries.forEachIndexed { index, entry ->
|
||||
key(entry) {
|
||||
val visibility = remember {
|
||||
MutableTransitionState<EntryVisibility>(EntryVisibility.Hidden)
|
||||
}
|
||||
visibility.targetState =
|
||||
when {
|
||||
currentPinEntries.isEmpty() && visiblePinEntries.size > 1 ->
|
||||
EntryVisibility.BulkHidden(index, visiblePinEntries.size)
|
||||
currentPinEntries.contains(entry) -> EntryVisibility.Shown
|
||||
else -> EntryVisibility.Hidden
|
||||
}
|
||||
|
||||
ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry"))
|
||||
|
||||
LaunchedEffect(entry) {
|
||||
// Remove entry from visiblePinEntries once the hide transition completed.
|
||||
snapshotFlow {
|
||||
visibility.currentState == visibility.targetState &&
|
||||
visibility.targetState != EntryVisibility.Shown
|
||||
}
|
||||
.collect { isRemoved ->
|
||||
if (isRemoved) {
|
||||
visiblePinEntries.remove(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntryVisibility {
|
||||
object Shown : EntryVisibility()
|
||||
|
||||
object Hidden : EntryVisibility()
|
||||
|
||||
/**
|
||||
* Same as [Hidden], but applies when multiple entries are hidden simultaneously, without
|
||||
* collapsing during the hide.
|
||||
*/
|
||||
data class BulkHidden(val staggerIndex: Int, val totalEntryCount: Int) : EntryVisibility()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
||||
// spec: http://shortn/_DEhE3Xl2bi
|
||||
val shapePadding = 6.dp
|
||||
val shapeOvershootSize = 22.dp
|
||||
val dismissStaggerDelayMs = 33
|
||||
val dismissDurationMs = 450
|
||||
val expansionDurationMs = 250
|
||||
val shapeExpandDurationMs = 83
|
||||
val shapeRetractDurationMs = 167
|
||||
val shapeCollapseDurationMs = 200
|
||||
|
||||
val animatedEntryWidth by
|
||||
transition.animateDp(
|
||||
transitionSpec = {
|
||||
when (val target = targetState) {
|
||||
is EntryVisibility.BulkHidden ->
|
||||
// only collapse horizontal space once all entries are removed
|
||||
snap(dismissDurationMs + dismissStaggerDelayMs * target.totalEntryCount)
|
||||
else -> tween(expansionDurationMs, easing = Easings.Standard)
|
||||
}
|
||||
},
|
||||
label = "entry space"
|
||||
) { state ->
|
||||
if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp
|
||||
}
|
||||
|
||||
val animatedShapeSize by
|
||||
transition.animateDp(
|
||||
transitionSpec = {
|
||||
when {
|
||||
EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown ->
|
||||
keyframes {
|
||||
durationMillis = shapeExpandDurationMs + shapeRetractDurationMs
|
||||
0.dp at 0 with Easings.Linear
|
||||
shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy
|
||||
}
|
||||
targetState is EntryVisibility.BulkHidden -> {
|
||||
val target = targetState as EntryVisibility.BulkHidden
|
||||
tween(
|
||||
dismissDurationMs,
|
||||
delayMillis = target.staggerIndex * dismissStaggerDelayMs,
|
||||
easing = Easings.Legacy,
|
||||
)
|
||||
}
|
||||
else -> tween(shapeCollapseDurationMs, easing = Easings.StandardDecelerate)
|
||||
}
|
||||
},
|
||||
label = "shape size"
|
||||
) { state ->
|
||||
when (state) {
|
||||
EntryVisibility.Shown -> entryShapeSize
|
||||
else -> 0.dp
|
||||
}
|
||||
}
|
||||
|
||||
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Layout(
|
||||
content = {
|
||||
// TODO(b/282730134): add support for dot shapes.
|
||||
Canvas(Modifier) { drawCircle(dotColor) }
|
||||
}
|
||||
) { measurables, _ ->
|
||||
val shapeSizePx = animatedShapeSize.roundToPx()
|
||||
val placeable = measurables.single().measure(Constraints.fixed(shapeSizePx, shapeSizePx))
|
||||
|
||||
layout(animatedEntryWidth.roundToPx(), entryShapeSize.roundToPx()) {
|
||||
placeable.place(
|
||||
((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
|
||||
((entryShapeSize - animatedShapeSize) / 2f).roundToPx()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinDigit(
|
||||
digit: Int,
|
||||
@@ -310,11 +428,13 @@ private fun showFailureAnimation() {
|
||||
// TODO(b/282730134): implement.
|
||||
}
|
||||
|
||||
private val entryShapeSize = 16.dp
|
||||
|
||||
private val pinButtonSize = 84.dp
|
||||
|
||||
// Pin button motion spec: http://shortn/_9TTIG6SoEa
|
||||
private val pinButtonPressedDuration = 100.milliseconds
|
||||
private val pinButtonPressedEasing = LinearEasing
|
||||
private val pinButtonPressedEasing = Easings.Linear
|
||||
private val pinButtonHoldTime = 33.milliseconds
|
||||
private val pinButtonReleasedDuration = 420.milliseconds
|
||||
private val pinButtonReleasedEasing = Easings.Standard
|
||||
|
||||
@@ -77,7 +77,7 @@ class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationReposit
|
||||
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
|
||||
|
||||
private val _authenticationMethod =
|
||||
MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234))
|
||||
MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.Pin(1234))
|
||||
override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||
_authenticationMethod.asStateFlow()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.authentication.domain.interactor
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
@@ -129,7 +130,7 @@ constructor(
|
||||
fun authenticate(input: List<Any>): Boolean {
|
||||
val isSuccessful =
|
||||
when (val authMethod = this.authenticationMethod.value) {
|
||||
is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code
|
||||
is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code
|
||||
is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
|
||||
is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
|
||||
else -> true
|
||||
@@ -177,15 +178,21 @@ constructor(
|
||||
|
||||
/**
|
||||
* Returns a PIN code from the given list. It's assumed the given list elements are all
|
||||
* [Int].
|
||||
* [Int] in the range [0-9].
|
||||
*/
|
||||
private fun List<Any>.asCode(): Int? {
|
||||
if (isEmpty()) {
|
||||
private fun List<Any>.asCode(): Long? {
|
||||
if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) {
|
||||
return null
|
||||
}
|
||||
|
||||
var code = 0
|
||||
map { it as Int }.forEach { integer -> code = code * 10 + integer }
|
||||
var code = 0L
|
||||
map {
|
||||
require(it is Int && it in 0..9) {
|
||||
"Pin is required to be Int in range [0..9], but got $it"
|
||||
}
|
||||
it
|
||||
}
|
||||
.forEach { integer -> code = code * 10 + integer }
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -32,7 +32,13 @@ sealed class AuthenticationMethodModel(
|
||||
/** The most basic authentication method. The lock screen can be swiped away when displayed. */
|
||||
object Swipe : AuthenticationMethodModel(isSecure = false)
|
||||
|
||||
data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true)
|
||||
/**
|
||||
* Authentication method using a PIN.
|
||||
*
|
||||
* In practice, a pin is restricted to 16 decimal digits , see
|
||||
* [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH]
|
||||
*/
|
||||
data class Pin(val code: Long) : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ constructor(
|
||||
|
||||
private fun promptMessage(authMethod: AuthenticationMethodModel): String {
|
||||
return when (authMethod) {
|
||||
is AuthenticationMethodModel.PIN ->
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
applicationContext.getString(R.string.keyguard_enter_your_pin)
|
||||
is AuthenticationMethodModel.Password ->
|
||||
applicationContext.getString(R.string.keyguard_enter_your_password)
|
||||
@@ -209,7 +209,7 @@ constructor(
|
||||
|
||||
private fun errorMessage(authMethod: AuthenticationMethodModel): String {
|
||||
return when (authMethod) {
|
||||
is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin)
|
||||
is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.kg_wrong_pin)
|
||||
is AuthenticationMethodModel.Password ->
|
||||
applicationContext.getString(R.string.kg_wrong_password)
|
||||
is AuthenticationMethodModel.Pattern ->
|
||||
|
||||
@@ -126,7 +126,7 @@ constructor(
|
||||
.map { model ->
|
||||
model?.let {
|
||||
when (interactor.authenticationMethod.value) {
|
||||
is AuthenticationMethodModel.PIN ->
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
R.string.kg_too_many_failed_pin_attempts_dialog_message
|
||||
is AuthenticationMethodModel.Password ->
|
||||
R.string.kg_too_many_failed_password_attempts_dialog_message
|
||||
@@ -165,7 +165,7 @@ constructor(
|
||||
authMethod: AuthenticationMethodModel,
|
||||
): AuthMethodBouncerViewModel? {
|
||||
return when (authMethod) {
|
||||
is AuthenticationMethodModel.PIN -> pin
|
||||
is AuthenticationMethodModel.Pin -> pin
|
||||
is AuthenticationMethodModel.Password -> password
|
||||
is AuthenticationMethodModel.Pattern -> pattern
|
||||
else -> null
|
||||
|
||||
@@ -16,18 +16,10 @@
|
||||
|
||||
package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.util.kotlin.pairwise
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Holds UI state and handles user input for the PIN code bouncer UI. */
|
||||
class PinBouncerViewModel(
|
||||
@@ -39,21 +31,8 @@ class PinBouncerViewModel(
|
||||
isInputEnabled = isInputEnabled,
|
||||
) {
|
||||
|
||||
private val entered = MutableStateFlow<List<Int>>(emptyList())
|
||||
/**
|
||||
* The length of the PIN digits that were input so far, two values are supplied the previous and
|
||||
* the current.
|
||||
*/
|
||||
val pinLengths: StateFlow<Pair<Int, Int>> =
|
||||
entered
|
||||
.pairwise()
|
||||
.map { it.previousValue.size to it.newValue.size }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = 0 to 0,
|
||||
)
|
||||
private var resetPinJob: Job? = null
|
||||
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
|
||||
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
|
||||
|
||||
/** Notifies that the UI has been shown to the user. */
|
||||
fun onShown() {
|
||||
@@ -62,47 +41,48 @@ class PinBouncerViewModel(
|
||||
|
||||
/** Notifies that the user clicked on a PIN button with the given digit value. */
|
||||
fun onPinButtonClicked(input: Int) {
|
||||
resetPinJob?.cancel()
|
||||
resetPinJob = null
|
||||
|
||||
if (entered.value.isEmpty()) {
|
||||
if (mutablePinEntries.value.isEmpty()) {
|
||||
interactor.clearMessage()
|
||||
}
|
||||
|
||||
entered.value += input
|
||||
mutablePinEntries.value += EnteredKey(input)
|
||||
}
|
||||
|
||||
/** Notifies that the user clicked the backspace button. */
|
||||
fun onBackspaceButtonClicked() {
|
||||
if (entered.value.isEmpty()) {
|
||||
if (mutablePinEntries.value.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
entered.value = entered.value.toMutableList().apply { removeLast() }
|
||||
mutablePinEntries.value = mutablePinEntries.value.toMutableList().apply { removeLast() }
|
||||
}
|
||||
|
||||
/** Notifies that the user long-pressed the backspace button. */
|
||||
fun onBackspaceButtonLongPressed() {
|
||||
resetPinJob?.cancel()
|
||||
resetPinJob =
|
||||
applicationScope.launch {
|
||||
while (entered.value.isNotEmpty()) {
|
||||
onBackspaceButtonClicked()
|
||||
delay(BACKSPACE_LONG_PRESS_DELAY_MS)
|
||||
}
|
||||
}
|
||||
mutablePinEntries.value = emptyList()
|
||||
}
|
||||
|
||||
/** Notifies that the user clicked the "enter" button. */
|
||||
fun onAuthenticateButtonClicked() {
|
||||
if (!interactor.authenticate(entered.value)) {
|
||||
if (!interactor.authenticate(mutablePinEntries.value.map { it.input })) {
|
||||
showFailureAnimation()
|
||||
}
|
||||
|
||||
entered.value = emptyList()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting const val BACKSPACE_LONG_PRESS_DELAY_MS = 80L
|
||||
mutablePinEntries.value = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private var nextSequenceNumber = 1
|
||||
|
||||
/**
|
||||
* The pin bouncer [input] as digits 0-9, together with a [sequenceNumber] to indicate the ordering.
|
||||
*
|
||||
* Since the model only allows appending/removing [EnteredKey]s from the end, the [sequenceNumber]
|
||||
* is strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at
|
||||
* a specific number.
|
||||
*/
|
||||
data class EnteredKey
|
||||
internal constructor(val input: Int, val sequenceNumber: Int = nextSequenceNumber++) :
|
||||
Comparable<EnteredKey> {
|
||||
override fun compareTo(other: EnteredKey): Int =
|
||||
compareValuesBy(this, other, EnteredKey::sequenceNumber)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.authentication.domain.interactor
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
@@ -48,7 +49,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun authMethod() =
|
||||
testScope.runTest {
|
||||
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234))
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin(1234))
|
||||
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
|
||||
@@ -147,7 +148,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
|
||||
@@ -160,7 +161,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
|
||||
@@ -168,6 +169,51 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withEmptyPin_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
assertThat(underTest.authenticate(listOf())).isFalse()
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectMaxLengthPin_returnsTrueAndUnlocksDevice() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(9999999999999999))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
|
||||
assertThat(isUnlocked).isTrue()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectTooLongPin_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
// Max pin length is 16 digits. To avoid issues with overflows, this test ensures
|
||||
// that all pins > 16 decimal digits are rejected.
|
||||
|
||||
// If the policy changes, there is work to do in SysUI.
|
||||
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
|
||||
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
underTest.setAuthenticationMethod(AuthenticationMethodModel.Pin(99999999999999999))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
|
||||
testScope.runTest {
|
||||
|
||||
@@ -69,7 +69,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -167,7 +167,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -211,7 +211,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
val throttling by collectLastValue(underTest.throttling)
|
||||
val message by collectLastValue(underTest.message)
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(throttling).isNull()
|
||||
assertThat(message).isEqualTo("")
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
@@ -55,7 +55,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun animateFailure() =
|
||||
testScope.runTest {
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
val animateFailure by collectLastValue(underTest.animateFailure)
|
||||
assertThat(animateFailure).isFalse()
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val message by collectLastValue(underTest.message)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(message?.isUpdateAnimated).isTrue()
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
@@ -120,7 +120,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(isInputEnabled).isTrue()
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
@@ -137,7 +137,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
fun throttlingDialogMessage() =
|
||||
testScope.runTest {
|
||||
val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
// Wrong PIN.
|
||||
@@ -154,7 +154,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
return listOf(
|
||||
AuthenticationMethodModel.None,
|
||||
AuthenticationMethodModel.Swipe,
|
||||
AuthenticationMethodModel.PIN(1234),
|
||||
AuthenticationMethodModel.Pin(1234),
|
||||
AuthenticationMethodModel.Password("password"),
|
||||
AuthenticationMethodModel.Pattern(
|
||||
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
|
||||
|
||||
@@ -25,12 +25,12 @@ import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.google.common.truth.Correspondence
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -85,8 +85,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -95,7 +95,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
underTest.onShown()
|
||||
|
||||
assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
|
||||
assertThat(pinLengths).isEqualTo(0 to 0)
|
||||
assertThat(entries).hasSize(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
}
|
||||
@@ -106,8 +106,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -117,7 +117,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
underTest.onPinButtonClicked(1)
|
||||
|
||||
assertThat(message?.text).isEmpty()
|
||||
assertThat(pinLengths).isEqualTo(0 to 1)
|
||||
assertThat(entries).hasSize(1)
|
||||
assertThat(entries?.map { it.input }).containsExactly(1)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
}
|
||||
@@ -128,32 +129,59 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
assertThat(pinLengths).isEqualTo(0 to 1)
|
||||
assertThat(entries).hasSize(1)
|
||||
|
||||
underTest.onBackspaceButtonClicked()
|
||||
|
||||
assertThat(message?.text).isEmpty()
|
||||
assertThat(pinLengths).isEqualTo(1 to 0)
|
||||
assertThat(entries).hasSize(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPinEdit() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
|
||||
underTest.onPinButtonClicked(1)
|
||||
underTest.onPinButtonClicked(2)
|
||||
underTest.onPinButtonClicked(3)
|
||||
underTest.onBackspaceButtonClicked()
|
||||
underTest.onBackspaceButtonClicked()
|
||||
underTest.onPinButtonClicked(4)
|
||||
underTest.onPinButtonClicked(5)
|
||||
|
||||
assertThat(entries).hasSize(3)
|
||||
assertThat(entries?.map { it.input }).containsExactly(1, 4, 5).inOrder()
|
||||
assertThat(entries?.map { it.sequenceNumber }).isInStrictOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBackspaceButtonLongPressed() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -165,13 +193,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
underTest.onPinButtonClicked(4)
|
||||
|
||||
underTest.onBackspaceButtonLongPressed()
|
||||
repeat(4) { index ->
|
||||
assertThat(pinLengths).isEqualTo(4 - index to 3 - index)
|
||||
advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
|
||||
}
|
||||
|
||||
assertThat(message?.text).isEmpty()
|
||||
assertThat(pinLengths).isEqualTo(1 to 0)
|
||||
assertThat(entries).hasSize(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
}
|
||||
@@ -181,7 +205,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -204,8 +228,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -219,7 +243,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
|
||||
underTest.onAuthenticateButtonClicked()
|
||||
|
||||
assertThat(pinLengths).isEqualTo(0 to 0)
|
||||
assertThat(entries).hasSize(0)
|
||||
assertThat(message?.text).isEqualTo(WRONG_PIN)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -231,8 +255,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val pinLengths by collectLastValue(underTest.pinLengths)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
@@ -245,7 +269,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
underTest.onPinButtonClicked(5) // PIN is now wrong!
|
||||
underTest.onAuthenticateButtonClicked()
|
||||
assertThat(message?.text).isEqualTo(WRONG_PIN)
|
||||
assertThat(pinLengths).isEqualTo(0 to 0)
|
||||
assertThat(entries).hasSize(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
|
||||
@@ -266,5 +290,11 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
private const val CONTAINER_NAME = "container1"
|
||||
private const val ENTER_YOUR_PIN = "Enter your pin"
|
||||
private const val WRONG_PIN = "Wrong pin"
|
||||
|
||||
val KEY_CODE =
|
||||
Correspondence.transforming<EnteredKey, Int>(
|
||||
{ it?.input },
|
||||
"has a eventId of",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.lockDevice()
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
@@ -108,7 +108,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.unlockDevice()
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
@@ -195,7 +195,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
|
||||
|
||||
@@ -109,7 +109,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||
@@ -119,7 +119,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -132,7 +132,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -145,7 +145,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -158,7 +158,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
runCurrent()
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -83,7 +83,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
runCurrent()
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_deviceLocked_lockScreen() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
@@ -81,7 +81,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_deviceUnlocked_gone() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
|
||||
@@ -91,7 +91,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.unlockDevice()
|
||||
runCurrent()
|
||||
|
||||
@@ -104,7 +104,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Pin(1234))
|
||||
authenticationInteractor.lockDevice()
|
||||
runCurrent()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user