Merge changes from topic "flexiglass-authmethod-is-a-flow" into udc-qpr-dev
* changes: [flexiglass] Skip lockscreen when devices wakes. [flexiglass] Adds authmethod flow.
This commit is contained in:
@@ -63,7 +63,7 @@ constructor(
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
|
||||
initialValue = destinationScenes(up = null)
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -77,12 +77,12 @@ constructor(
|
||||
}
|
||||
|
||||
private fun destinationScenes(
|
||||
up: SceneKey,
|
||||
up: SceneKey?,
|
||||
): Map<UserAction, SceneModel> {
|
||||
return mapOf(
|
||||
UserAction.Swipe(Direction.UP) to SceneModel(up),
|
||||
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
|
||||
)
|
||||
return buildMap {
|
||||
up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
|
||||
this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2023 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.authentication.data.model
|
||||
|
||||
/** Enumerates all known authentication methods. */
|
||||
sealed class AuthenticationMethodModel(
|
||||
/**
|
||||
* Whether the authentication method is considered to be "secure".
|
||||
*
|
||||
* "Secure" authentication methods require authentication to unlock the device. Non-secure auth
|
||||
* methods simply require user dismissal.
|
||||
*/
|
||||
open val isSecure: Boolean,
|
||||
) {
|
||||
/** There is no authentication method on the device. We shouldn't even show the lock screen. */
|
||||
object None : AuthenticationMethodModel(isSecure = false)
|
||||
|
||||
object Pin : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
object Password : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
object Pattern : AuthenticationMethodModel(isSecure = true)
|
||||
}
|
||||
@@ -14,15 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.authentication.data.repository
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.IntentFilter
|
||||
import android.os.UserHandle
|
||||
import com.android.internal.widget.LockPatternChecker
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
@@ -37,13 +43,17 @@ import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -54,9 +64,10 @@ interface AuthenticationRepository {
|
||||
* Whether the device is unlocked.
|
||||
*
|
||||
* A device that is not yet unlocked requires unlocking by completing an authentication
|
||||
* challenge according to the current authentication method.
|
||||
*
|
||||
* Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
|
||||
* challenge according to the current authentication method, unless in cases when the current
|
||||
* authentication method is not "secure" (for example, None); in such cases, the value of this
|
||||
* flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
|
||||
* by the user to proceed.
|
||||
*/
|
||||
val isUnlocked: StateFlow<Boolean>
|
||||
|
||||
@@ -85,9 +96,30 @@ interface AuthenticationRepository {
|
||||
/** The current throttling state, as cached via [setThrottling]. */
|
||||
val throttling: StateFlow<AuthenticationThrottlingModel>
|
||||
|
||||
/**
|
||||
* The currently-configured authentication method. This determines how the authentication
|
||||
* challenge needs to be completed in order to unlock an otherwise locked device.
|
||||
*
|
||||
* Note: there may be other ways to unlock the device that "bypass" the need for this
|
||||
* authentication challenge (notably, biometrics like fingerprint or face unlock).
|
||||
*
|
||||
* Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
|
||||
* snapshot of the current authentication method without establishing a collector of the flow
|
||||
* can do so by invoking [getAuthenticationMethod].
|
||||
*/
|
||||
val authenticationMethod: Flow<AuthenticationMethodModel>
|
||||
|
||||
/**
|
||||
* Returns the currently-configured authentication method. This determines how the
|
||||
* authentication challenge is completed in order to unlock an otherwise locked device.
|
||||
* authentication challenge needs to be completed in order to unlock an otherwise locked device.
|
||||
*
|
||||
* Note: there may be other ways to unlock the device that "bypass" the need for this
|
||||
* authentication challenge (notably, biometrics like fingerprint or face unlock).
|
||||
*
|
||||
* Note: by design, this is offered as a convenience method alongside [authenticationMethod].
|
||||
* The flow should be used for code that wishes to stay up-to-date its logic as the
|
||||
* authentication changes over time and this method should be used for simple code that only
|
||||
* needs to check the current value.
|
||||
*/
|
||||
suspend fun getAuthenticationMethod(): AuthenticationMethodModel
|
||||
|
||||
@@ -141,6 +173,7 @@ constructor(
|
||||
private val userRepository: UserRepository,
|
||||
keyguardRepository: KeyguardRepository,
|
||||
private val lockPatternUtils: LockPatternUtils,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
) : AuthenticationRepository {
|
||||
|
||||
override val isUnlocked = keyguardRepository.isKeyguardUnlocked
|
||||
@@ -148,7 +181,7 @@ constructor(
|
||||
override suspend fun isLockscreenEnabled(): Boolean {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
!lockPatternUtils.isLockPatternEnabled(selectedUserId)
|
||||
!lockPatternUtils.isLockScreenDisabled(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,18 +205,31 @@ constructor(
|
||||
private val UserRepository.selectedUserId: Int
|
||||
get() = getSelectedUserInfo().id
|
||||
|
||||
override val authenticationMethod: Flow<AuthenticationMethodModel> =
|
||||
userRepository.selectedUserInfo
|
||||
.map { it.id }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { selectedUserId ->
|
||||
broadcastDispatcher
|
||||
.broadcastFlow(
|
||||
filter =
|
||||
IntentFilter(
|
||||
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
|
||||
),
|
||||
user = UserHandle.of(selectedUserId),
|
||||
)
|
||||
.onStart { emit(Unit) }
|
||||
.map { selectedUserId }
|
||||
}
|
||||
.map { selectedUserId ->
|
||||
withContext(backgroundDispatcher) {
|
||||
blockingAuthenticationMethodInternal(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
when (getSecurityMode.apply(selectedUserId)) {
|
||||
KeyguardSecurityModel.SecurityMode.PIN,
|
||||
KeyguardSecurityModel.SecurityMode.SimPin,
|
||||
KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
|
||||
KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
|
||||
KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
|
||||
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
|
||||
KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
|
||||
}
|
||||
blockingAuthenticationMethodInternal(userRepository.selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +347,27 @@ constructor(
|
||||
|
||||
return flow.asStateFlow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication method for the given user ID.
|
||||
*
|
||||
* WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main
|
||||
* thread. We keep it not marked as `suspend` because we want to be able to run this without a
|
||||
* `runBlocking` which has a ton of performance/blocking problems.
|
||||
*/
|
||||
private fun blockingAuthenticationMethodInternal(
|
||||
userId: Int,
|
||||
): AuthenticationMethodModel {
|
||||
return when (getSecurityMode.apply(userId)) {
|
||||
KeyguardSecurityModel.SecurityMode.PIN,
|
||||
KeyguardSecurityModel.SecurityMode.SimPin,
|
||||
KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
|
||||
KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
|
||||
KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
|
||||
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
|
||||
KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
|
||||
@@ -18,8 +18,10 @@ package com.android.systemui.authentication.domain.interactor
|
||||
|
||||
import com.android.internal.widget.LockPatternView
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
@@ -35,8 +37,10 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -55,22 +59,41 @@ constructor(
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
private val clock: SystemClock,
|
||||
) {
|
||||
/**
|
||||
* The currently-configured authentication method. This determines how the authentication
|
||||
* challenge needs to be completed in order to unlock an otherwise locked device.
|
||||
*
|
||||
* Note: there may be other ways to unlock the device that "bypass" the need for this
|
||||
* authentication challenge (notably, biometrics like fingerprint or face unlock).
|
||||
*
|
||||
* Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
|
||||
* snapshot of the current authentication method without establishing a collector of the flow
|
||||
* can do so by invoking [getAuthenticationMethod].
|
||||
*
|
||||
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
|
||||
* the current authentication method is "swipe", the user does not need to complete any
|
||||
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
|
||||
* get past it. This also means that the value of [isUnlocked] remains `false` even when the
|
||||
* lockscreen is showing and still needs to be dismissed by the user to proceed.
|
||||
*/
|
||||
val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
|
||||
repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
|
||||
|
||||
/**
|
||||
* Whether the device is unlocked.
|
||||
*
|
||||
* A device that is not yet unlocked requires unlocking by completing an authentication
|
||||
* challenge according to the current authentication method.
|
||||
*
|
||||
* Note that this state has no real bearing on whether the lock screen is showing or dismissed.
|
||||
* challenge according to the current authentication method, unless in cases when the current
|
||||
* authentication method is not "secure" (for example, None and Swipe); in such cases, the value
|
||||
* of this flow will always be `true`, even if the lockscreen is showing and still needs to be
|
||||
* dismissed by the user to proceed.
|
||||
*/
|
||||
val isUnlocked: StateFlow<Boolean> =
|
||||
repository.isUnlocked
|
||||
.map { isUnlocked ->
|
||||
if (getAuthenticationMethod() is AuthenticationMethodModel.None) {
|
||||
true
|
||||
} else {
|
||||
isUnlocked
|
||||
}
|
||||
combine(
|
||||
repository.isUnlocked,
|
||||
authenticationMethod,
|
||||
) { isUnlocked, authenticationMethod ->
|
||||
authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
@@ -129,18 +152,24 @@ constructor(
|
||||
|
||||
/**
|
||||
* Returns the currently-configured authentication method. This determines how the
|
||||
* authentication challenge is completed in order to unlock an otherwise locked device.
|
||||
* authentication challenge needs to be completed in order to unlock an otherwise locked device.
|
||||
*
|
||||
* Note: there may be other ways to unlock the device that "bypass" the need for this
|
||||
* authentication challenge (notably, biometrics like fingerprint or face unlock).
|
||||
*
|
||||
* Note: by design, this is offered as a convenience method alongside [authenticationMethod].
|
||||
* The flow should be used for code that wishes to stay up-to-date its logic as the
|
||||
* authentication changes over time and this method should be used for simple code that only
|
||||
* needs to check the current value.
|
||||
*
|
||||
* Note: this layer adds the synthetic authentication method of "swipe" which is special. When
|
||||
* the current authentication method is "swipe", the user does not need to complete any
|
||||
* authentication challenge to unlock the device; they just need to dismiss the lockscreen to
|
||||
* get past it. This also means that the value of [isUnlocked] remains `false` even when the
|
||||
* lockscreen is showing and still needs to be dismissed by the user to proceed.
|
||||
*/
|
||||
suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
val authMethod = repository.getAuthenticationMethod()
|
||||
return if (
|
||||
authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
|
||||
) {
|
||||
// We treat "None" as "Swipe" when the lockscreen is enabled.
|
||||
AuthenticationMethodModel.Swipe
|
||||
} else {
|
||||
authMethod
|
||||
}
|
||||
suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
|
||||
return repository.getAuthenticationMethod().toDomainLayer()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,21 +299,38 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun AuthenticationMethodModel.createCredential(
|
||||
private fun DomainLayerAuthenticationMethodModel.createCredential(
|
||||
input: List<Any>
|
||||
): LockscreenCredential? {
|
||||
return when (this) {
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
is DomainLayerAuthenticationMethodModel.Pin ->
|
||||
LockscreenCredential.createPin(input.joinToString(""))
|
||||
is AuthenticationMethodModel.Password ->
|
||||
is DomainLayerAuthenticationMethodModel.Password ->
|
||||
LockscreenCredential.createPassword(input.joinToString(""))
|
||||
is AuthenticationMethodModel.Pattern ->
|
||||
is DomainLayerAuthenticationMethodModel.Pattern ->
|
||||
LockscreenCredential.createPattern(
|
||||
input
|
||||
.map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
|
||||
.map { it as AuthenticationPatternCoordinate }
|
||||
.map { LockPatternView.Cell.of(it.y, it.x) }
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer():
|
||||
DomainLayerAuthenticationMethodModel {
|
||||
return when (this) {
|
||||
is DataLayerAuthenticationMethodModel.None ->
|
||||
if (repository.isLockscreenEnabled()) {
|
||||
DomainLayerAuthenticationMethodModel.Swipe
|
||||
} else {
|
||||
DomainLayerAuthenticationMethodModel.None
|
||||
}
|
||||
is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin
|
||||
is DataLayerAuthenticationMethodModel.Password ->
|
||||
DomainLayerAuthenticationMethodModel.Password
|
||||
is DataLayerAuthenticationMethodModel.Pattern ->
|
||||
DomainLayerAuthenticationMethodModel.Pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.authentication.shared.model
|
||||
package com.android.systemui.authentication.domain.model
|
||||
|
||||
/** Enumerates all known authentication methods. */
|
||||
sealed class AuthenticationMethodModel(
|
||||
@@ -36,10 +36,5 @@ sealed class AuthenticationMethodModel(
|
||||
|
||||
object Password : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
object Pattern : AuthenticationMethodModel(isSecure = true) {
|
||||
data class PatternCoordinate(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
)
|
||||
}
|
||||
object Pattern : AuthenticationMethodModel(isSecure = true)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.authentication.shared.model
|
||||
|
||||
data class AuthenticationPatternCoordinate(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
)
|
||||
@@ -14,14 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.bouncer.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.bouncer.data.repository.BouncerRepository
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
@@ -35,7 +33,6 @@ import com.android.systemui.util.kotlin.pairwise
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -106,14 +103,6 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently-configured authentication method. This determines how the
|
||||
* authentication challenge is completed in order to unlock an otherwise locked device.
|
||||
*/
|
||||
suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
return authenticationInteractor.getAuthenticationMethod()
|
||||
}
|
||||
|
||||
/**
|
||||
* Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
|
||||
*
|
||||
@@ -124,7 +113,9 @@ constructor(
|
||||
) {
|
||||
applicationScope.launch {
|
||||
if (authenticationInteractor.isAuthenticationRequired()) {
|
||||
repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
|
||||
repository.setMessage(
|
||||
message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
|
||||
)
|
||||
sceneInteractor.setCurrentScene(
|
||||
scene = SceneModel(SceneKey.Bouncer),
|
||||
loggingReason = "request to unlock device while authentication required",
|
||||
@@ -143,7 +134,9 @@ constructor(
|
||||
* method.
|
||||
*/
|
||||
fun resetMessage() {
|
||||
applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) }
|
||||
applicationScope.launch {
|
||||
repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes the user-facing message. */
|
||||
@@ -181,7 +174,7 @@ constructor(
|
||||
loggingReason = "successful authentication",
|
||||
)
|
||||
} else {
|
||||
repository.setMessage(errorMessage(getAuthenticationMethod()))
|
||||
repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
|
||||
}
|
||||
|
||||
return isAuthenticated
|
||||
|
||||
@@ -14,25 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.util.kotlin.pairwise
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.ceil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -50,23 +45,24 @@ class BouncerViewModel
|
||||
constructor(
|
||||
@Application private val applicationContext: Context,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val interactor: BouncerInteractor,
|
||||
private val bouncerInteractor: BouncerInteractor,
|
||||
private val authenticationInteractor: AuthenticationInteractor,
|
||||
featureFlags: FeatureFlags,
|
||||
) {
|
||||
private val isInputEnabled: StateFlow<Boolean> =
|
||||
interactor.isThrottled
|
||||
bouncerInteractor.isThrottled
|
||||
.map { !it }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = !interactor.isThrottled.value,
|
||||
initialValue = !bouncerInteractor.isThrottled.value,
|
||||
)
|
||||
|
||||
private val pin: PinBouncerViewModel by lazy {
|
||||
PinBouncerViewModel(
|
||||
applicationContext = applicationContext,
|
||||
applicationScope = applicationScope,
|
||||
interactor = interactor,
|
||||
interactor = bouncerInteractor,
|
||||
isInputEnabled = isInputEnabled,
|
||||
)
|
||||
}
|
||||
@@ -74,7 +70,7 @@ constructor(
|
||||
private val password: PasswordBouncerViewModel by lazy {
|
||||
PasswordBouncerViewModel(
|
||||
applicationScope = applicationScope,
|
||||
interactor = interactor,
|
||||
interactor = bouncerInteractor,
|
||||
isInputEnabled = isInputEnabled,
|
||||
)
|
||||
}
|
||||
@@ -83,31 +79,35 @@ constructor(
|
||||
PatternBouncerViewModel(
|
||||
applicationContext = applicationContext,
|
||||
applicationScope = applicationScope,
|
||||
interactor = interactor,
|
||||
interactor = bouncerInteractor,
|
||||
isInputEnabled = isInputEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
/** View-model for the current UI, based on the current authentication method. */
|
||||
private val _authMethod =
|
||||
MutableSharedFlow<AuthMethodBouncerViewModel?>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
|
||||
_authMethod.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null,
|
||||
)
|
||||
authenticationInteractor.authenticationMethod
|
||||
.map { authenticationMethod ->
|
||||
when (authenticationMethod) {
|
||||
is AuthenticationMethodModel.Pin -> pin
|
||||
is AuthenticationMethodModel.Password -> password
|
||||
is AuthenticationMethodModel.Pattern -> pattern
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
init {
|
||||
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
|
||||
applicationScope.launch {
|
||||
interactor.isThrottled
|
||||
bouncerInteractor.isThrottled
|
||||
.map { isThrottled ->
|
||||
if (isThrottled) {
|
||||
when (interactor.getAuthenticationMethod()) {
|
||||
when (authenticationInteractor.getAuthenticationMethod()) {
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
R.string.kg_too_many_failed_pin_attempts_dialog_message
|
||||
is AuthenticationMethodModel.Password ->
|
||||
@@ -118,8 +118,9 @@ constructor(
|
||||
}?.let { stringResourceId ->
|
||||
applicationContext.getString(
|
||||
stringResourceId,
|
||||
interactor.throttling.value.failedAttemptCount,
|
||||
ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
|
||||
bouncerInteractor.throttling.value.failedAttemptCount,
|
||||
ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
|
||||
.toInt(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -133,25 +134,14 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applicationScope.launch {
|
||||
_authMethod.subscriptionCount
|
||||
.pairwise()
|
||||
.map { (previousCount, currentCount) -> currentCount > previousCount }
|
||||
.collect { subscriberAdded ->
|
||||
if (subscriberAdded) {
|
||||
reloadAuthMethod()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The user-facing message to show in the bouncer. */
|
||||
val message: StateFlow<MessageViewModel> =
|
||||
combine(
|
||||
interactor.message,
|
||||
interactor.isThrottled,
|
||||
bouncerInteractor.message,
|
||||
bouncerInteractor.isThrottled,
|
||||
) { message, isThrottled ->
|
||||
toMessageViewModel(message, isThrottled)
|
||||
}
|
||||
@@ -160,8 +150,8 @@ constructor(
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue =
|
||||
toMessageViewModel(
|
||||
message = interactor.message.value,
|
||||
isThrottled = interactor.isThrottled.value,
|
||||
message = bouncerInteractor.message.value,
|
||||
isThrottled = bouncerInteractor.isThrottled.value,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -197,17 +187,6 @@ constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun reloadAuthMethod() {
|
||||
_authMethod.tryEmit(
|
||||
when (interactor.getAuthenticationMethod()) {
|
||||
is AuthenticationMethodModel.Pin -> pin
|
||||
is AuthenticationMethodModel.Password -> password
|
||||
is AuthenticationMethodModel.Pattern -> pattern
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
data class MessageViewModel(
|
||||
val text: String,
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
@@ -193,8 +193,8 @@ data class PatternDotViewModel(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
) {
|
||||
fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate {
|
||||
return AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||
fun toCoordinate(): AuthenticationPatternCoordinate {
|
||||
return AuthenticationPatternCoordinate(
|
||||
x = x,
|
||||
y = y,
|
||||
)
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.keyguard.domain.interactor
|
||||
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/** Hosts business and application state accessing logic for the lockscreen scene. */
|
||||
@SysUISingleton
|
||||
class LockscreenSceneInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Application applicationScope: CoroutineScope,
|
||||
private val authenticationInteractor: AuthenticationInteractor,
|
||||
private val bouncerInteractor: BouncerInteractor,
|
||||
) {
|
||||
/** Whether the device is currently locked. */
|
||||
val isDeviceLocked: StateFlow<Boolean> =
|
||||
authenticationInteractor.isUnlocked
|
||||
.map { !it }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = !authenticationInteractor.isUnlocked.value,
|
||||
)
|
||||
|
||||
/** Whether it's currently possible to swipe up to dismiss the lockscreen. */
|
||||
val isSwipeToDismissEnabled: StateFlow<Boolean> =
|
||||
authenticationInteractor.isUnlocked
|
||||
.map { isUnlocked ->
|
||||
!isUnlocked &&
|
||||
authenticationInteractor.getAuthenticationMethod() is
|
||||
AuthenticationMethodModel.Swipe
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
/** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
|
||||
fun dismissLockscreen() {
|
||||
bouncerInteractor.showOrUnlockDevice()
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,17 @@
|
||||
package com.android.systemui.keyguard.ui.viewmodel
|
||||
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -36,36 +39,37 @@ class LockscreenSceneViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@Application applicationScope: CoroutineScope,
|
||||
private val interactor: LockscreenSceneInteractor,
|
||||
authenticationInteractor: AuthenticationInteractor,
|
||||
private val bouncerInteractor: BouncerInteractor,
|
||||
) {
|
||||
/** The icon for the "lock" button on the lockscreen. */
|
||||
val lockButtonIcon: StateFlow<Icon> =
|
||||
interactor.isDeviceLocked
|
||||
.map { isLocked -> lockIcon(isLocked = isLocked) }
|
||||
authenticationInteractor.isUnlocked
|
||||
.map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value),
|
||||
initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value),
|
||||
)
|
||||
|
||||
/** The key of the scene we should switch to when swiping up. */
|
||||
val upDestinationSceneKey: StateFlow<SceneKey> =
|
||||
interactor.isSwipeToDismissEnabled
|
||||
.map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value),
|
||||
)
|
||||
val upDestinationSceneKey: Flow<SceneKey> =
|
||||
authenticationInteractor.authenticationMethod.map { authenticationMethod ->
|
||||
if (authenticationMethod is AuthenticationMethodModel.Swipe) {
|
||||
SceneKey.Gone
|
||||
} else {
|
||||
SceneKey.Bouncer
|
||||
}
|
||||
}
|
||||
|
||||
/** Notifies that the lock button on the lock screen was clicked. */
|
||||
fun onLockButtonClicked() {
|
||||
interactor.dismissLockscreen()
|
||||
bouncerInteractor.showOrUnlockDevice()
|
||||
}
|
||||
|
||||
/** Notifies that some content on the lock screen was clicked. */
|
||||
fun onContentClicked() {
|
||||
interactor.dismissLockscreen()
|
||||
bouncerInteractor.showOrUnlockDevice()
|
||||
}
|
||||
|
||||
private fun upDestinationSceneKey(
|
||||
@@ -75,22 +79,22 @@ constructor(
|
||||
}
|
||||
|
||||
private fun lockIcon(
|
||||
isLocked: Boolean,
|
||||
isUnlocked: Boolean,
|
||||
): Icon {
|
||||
return Icon.Resource(
|
||||
res =
|
||||
if (isLocked) {
|
||||
R.drawable.ic_device_lock_on
|
||||
} else {
|
||||
if (isUnlocked) {
|
||||
R.drawable.ic_device_lock_off
|
||||
} else {
|
||||
R.drawable.ic_device_lock_on
|
||||
},
|
||||
contentDescription =
|
||||
ContentDescription.Resource(
|
||||
res =
|
||||
if (isLocked) {
|
||||
R.string.accessibility_lock_icon
|
||||
} else {
|
||||
if (isUnlocked) {
|
||||
R.string.accessibility_unlock_button
|
||||
} else {
|
||||
R.string.accessibility_lock_icon
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -16,19 +16,17 @@
|
||||
|
||||
package com.android.systemui.qs.ui.viewmodel
|
||||
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Models UI state and handles user input for the quick settings scene. */
|
||||
@SysUISingleton
|
||||
class QuickSettingsSceneViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val lockscreenSceneInteractor: LockscreenSceneInteractor,
|
||||
) {
|
||||
constructor(private val bouncerInteractor: BouncerInteractor) {
|
||||
/** Notifies that some content in quick settings was clicked. */
|
||||
fun onContentClicked() {
|
||||
lockscreenSceneInteractor.dismissLockscreen()
|
||||
bouncerInteractor.showOrUnlockDevice()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.startable
|
||||
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.DisplayId
|
||||
@@ -135,22 +136,27 @@ constructor(
|
||||
|
||||
applicationScope.launch {
|
||||
keyguardInteractor.wakefulnessModel
|
||||
.map { it.state == WakefulnessState.ASLEEP }
|
||||
.map { wakefulnessModel -> wakefulnessModel.state }
|
||||
.distinctUntilChanged()
|
||||
.collect { isAsleep ->
|
||||
if (isAsleep) {
|
||||
// When the device goes to sleep, reset the current scene.
|
||||
val isUnlocked = authenticationInteractor.isUnlocked.value
|
||||
val (targetSceneKey, loggingReason) =
|
||||
if (isUnlocked) {
|
||||
SceneKey.Gone to "device is asleep while unlocked"
|
||||
} else {
|
||||
SceneKey.Lockscreen to "device is asleep while locked"
|
||||
.collect { wakefulnessState ->
|
||||
when (wakefulnessState) {
|
||||
WakefulnessState.STARTING_TO_SLEEP -> {
|
||||
switchToScene(
|
||||
targetSceneKey = SceneKey.Lockscreen,
|
||||
loggingReason = "device is asleep",
|
||||
)
|
||||
}
|
||||
WakefulnessState.STARTING_TO_WAKE -> {
|
||||
val authMethod = authenticationInteractor.getAuthenticationMethod()
|
||||
if (authMethod == AuthenticationMethodModel.None) {
|
||||
switchToScene(
|
||||
targetSceneKey = SceneKey.Gone,
|
||||
loggingReason =
|
||||
"device is starting to wake up while auth method is None",
|
||||
)
|
||||
}
|
||||
switchToScene(
|
||||
targetSceneKey = targetSceneKey,
|
||||
loggingReason = loggingReason,
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package com.android.systemui.shade.ui.viewmodel
|
||||
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -33,29 +34,30 @@ class ShadeSceneViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val lockscreenSceneInteractor: LockscreenSceneInteractor,
|
||||
authenticationInteractor: AuthenticationInteractor,
|
||||
private val bouncerInteractor: BouncerInteractor,
|
||||
) {
|
||||
/** The key of the scene we should switch to when swiping up. */
|
||||
val upDestinationSceneKey: StateFlow<SceneKey> =
|
||||
lockscreenSceneInteractor.isDeviceLocked
|
||||
.map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
|
||||
authenticationInteractor.isUnlocked
|
||||
.map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue =
|
||||
upDestinationSceneKey(
|
||||
isLocked = lockscreenSceneInteractor.isDeviceLocked.value,
|
||||
isUnlocked = authenticationInteractor.isUnlocked.value,
|
||||
),
|
||||
)
|
||||
|
||||
/** Notifies that some content in the shade was clicked. */
|
||||
fun onContentClicked() {
|
||||
lockscreenSceneInteractor.dismissLockscreen()
|
||||
bouncerInteractor.showOrUnlockDevice()
|
||||
}
|
||||
|
||||
private fun upDestinationSceneKey(
|
||||
isLocked: Boolean,
|
||||
isUnlocked: Boolean,
|
||||
): SceneKey {
|
||||
return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
|
||||
return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,30 @@
|
||||
|
||||
package com.android.systemui.authentication.data.repository
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.UserInfo
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.coroutines.collectValues
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.function.Function
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@@ -43,6 +50,7 @@ import org.mockito.MockitoAnnotations
|
||||
class AuthenticationRepositoryTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||
@Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
|
||||
|
||||
private val testUtils = SceneTestUtils(this)
|
||||
private val testScope = testUtils.testScope
|
||||
@@ -50,23 +58,48 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: AuthenticationRepository
|
||||
|
||||
private var currentSecurityMode: KeyguardSecurityModel.SecurityMode =
|
||||
KeyguardSecurityModel.SecurityMode.PIN
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
userRepository.setUserInfos(USER_INFOS)
|
||||
runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
|
||||
whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
|
||||
|
||||
underTest =
|
||||
AuthenticationRepositoryImpl(
|
||||
applicationScope = testScope.backgroundScope,
|
||||
getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN },
|
||||
getSecurityMode = getSecurityMode,
|
||||
backgroundDispatcher = testUtils.testDispatcher,
|
||||
userRepository = userRepository,
|
||||
keyguardRepository = testUtils.keyguardRepository,
|
||||
lockPatternUtils = lockPatternUtils,
|
||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticationMethod() =
|
||||
testScope.runTest {
|
||||
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||
runCurrent()
|
||||
dispatchBroadcast()
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
|
||||
assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
|
||||
|
||||
setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern)
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Pattern)
|
||||
|
||||
setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None)
|
||||
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.None)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isAutoConfirmEnabled() =
|
||||
testScope.runTest {
|
||||
@@ -95,6 +128,20 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
|
||||
assertThat(values.last()).isTrue()
|
||||
}
|
||||
|
||||
private fun setSecurityModeAndDispatchBroadcast(
|
||||
securityMode: KeyguardSecurityModel.SecurityMode,
|
||||
) {
|
||||
currentSecurityMode = securityMode
|
||||
dispatchBroadcast()
|
||||
}
|
||||
|
||||
private fun dispatchBroadcast() {
|
||||
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
|
||||
context,
|
||||
Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val USER_INFOS =
|
||||
listOf(
|
||||
|
||||
@@ -19,9 +19,11 @@ 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.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
@@ -50,42 +52,61 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun getAuthenticationMethod() =
|
||||
fun authenticationMethod() =
|
||||
testScope.runTest {
|
||||
assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
|
||||
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||
runCurrent()
|
||||
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Password)
|
||||
.isEqualTo(DomainLayerAuthenticationMethodModel.Password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
|
||||
fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||
runCurrent()
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
|
||||
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Swipe)
|
||||
.isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAuthenticationMethod_none_whenLockscreenDisabled() =
|
||||
fun authenticationMethod_none_whenLockscreenDisabled() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||
runCurrent()
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
utils.authenticationRepository.setLockscreenEnabled(false)
|
||||
|
||||
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.None)
|
||||
.isEqualTo(DomainLayerAuthenticationMethodModel.None)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
utils.authenticationRepository.setLockscreenEnabled(false)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
@@ -111,7 +132,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
@@ -124,7 +147,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isTrue()
|
||||
@@ -135,7 +158,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||
}
|
||||
@@ -146,7 +171,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||
@@ -157,7 +182,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||
}
|
||||
@@ -166,7 +193,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun authenticate_withCorrectPin_returnsTrue() =
|
||||
testScope.runTest {
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
|
||||
assertThat(isThrottled).isFalse()
|
||||
}
|
||||
@@ -174,21 +203,27 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun authenticate_withIncorrectPin_returnsFalse() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun authenticate_withEmptyPin_throwsException() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
underTest.authenticate(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
val pin = List(16) { 9 }
|
||||
utils.authenticationRepository.overrideCredential(pin)
|
||||
assertThat(underTest.authenticate(pin)).isTrue()
|
||||
@@ -203,7 +238,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
// If the policy changes, there is work to do in SysUI.
|
||||
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
|
||||
}
|
||||
|
||||
@@ -212,7 +249,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("password".toList())).isTrue()
|
||||
@@ -223,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun authenticate_withIncorrectPassword_returnsFalse() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
|
||||
@@ -233,7 +270,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun authenticate_withCorrectPattern_returnsTrue() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern
|
||||
DataLayerAuthenticationMethodModel.Pattern
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
|
||||
@@ -243,21 +280,21 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun authenticate_withIncorrectPattern_returnsFalse() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern
|
||||
DataLayerAuthenticationMethodModel.Pattern
|
||||
)
|
||||
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
listOf(
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||
AuthenticationPatternCoordinate(
|
||||
x = 2,
|
||||
y = 0,
|
||||
),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||
AuthenticationPatternCoordinate(
|
||||
x = 2,
|
||||
y = 1,
|
||||
),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||
AuthenticationPatternCoordinate(
|
||||
x = 2,
|
||||
y = 2,
|
||||
),
|
||||
@@ -271,7 +308,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
|
||||
testScope.runTest {
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
@@ -289,7 +328,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
@@ -305,7 +346,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
@@ -321,7 +364,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
@@ -337,7 +382,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(false)
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
@@ -354,7 +401,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
|
||||
@@ -367,7 +414,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
val throttling by collectLastValue(underTest.throttling)
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
|
||||
assertThat(isUnlocked).isTrue()
|
||||
assertThat(isThrottled).isFalse()
|
||||
@@ -456,7 +505,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withoutAutoConfirm_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(false)
|
||||
|
||||
assertThat(hintedPinLength).isNull()
|
||||
@@ -466,7 +517,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.overrideCredential(
|
||||
buildList {
|
||||
repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
|
||||
@@ -481,7 +534,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
utils.authenticationRepository.overrideCredential(
|
||||
buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
|
||||
@@ -494,7 +549,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
utils.authenticationRepository.overrideCredential(
|
||||
buildList {
|
||||
repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
|
||||
|
||||
@@ -19,8 +19,9 @@ package com.android.systemui.bouncer.domain.interactor
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
@@ -73,6 +74,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -104,6 +106,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice()
|
||||
@@ -140,6 +143,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -170,6 +174,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -202,6 +207,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -214,11 +220,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||
|
||||
// Wrong input.
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
|
||||
)
|
||||
)
|
||||
assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2))))
|
||||
.isFalse()
|
||||
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
@@ -248,7 +250,8 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
underTest.showOrUnlockDevice()
|
||||
@@ -264,6 +267,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
val customMessage = "Hello there!"
|
||||
|
||||
@@ -18,19 +18,17 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class AuthMethodBouncerViewModelTest : SysuiTestCase() {
|
||||
|
||||
@@ -18,8 +18,9 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -55,6 +56,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
private val underTest =
|
||||
utils.bouncerViewModel(
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -86,7 +88,8 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun authMethod_reusesInstances() =
|
||||
testScope.runTest {
|
||||
val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>()
|
||||
val seen =
|
||||
mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>()
|
||||
val authMethodViewModel: AuthMethodBouncerViewModel? by
|
||||
collectLastValue(underTest.authMethod)
|
||||
// First pass, populate our "seen" map:
|
||||
@@ -105,7 +108,7 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() {
|
||||
assertThat(authMethodsToTest().map { it::class }.toSet())
|
||||
.isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
|
||||
.isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,7 +116,9 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val message by collectLastValue(underTest.message)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
assertThat(message?.isUpdateAnimated).isTrue()
|
||||
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
@@ -136,7 +141,9 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
assertThat(isInputEnabled).isTrue()
|
||||
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
@@ -153,7 +160,9 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
fun throttlingDialogMessage() =
|
||||
testScope.runTest {
|
||||
val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
)
|
||||
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
// Wrong PIN.
|
||||
@@ -166,13 +175,32 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
assertThat(throttlingDialogMessage).isNull()
|
||||
}
|
||||
|
||||
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
|
||||
private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> {
|
||||
return listOf(
|
||||
AuthenticationMethodModel.None,
|
||||
AuthenticationMethodModel.Swipe,
|
||||
AuthenticationMethodModel.Pin,
|
||||
AuthenticationMethodModel.Password,
|
||||
AuthenticationMethodModel.Pattern,
|
||||
DomainLayerAuthenticationMethodModel.None,
|
||||
DomainLayerAuthenticationMethodModel.Swipe,
|
||||
DomainLayerAuthenticationMethodModel.Pin,
|
||||
DomainLayerAuthenticationMethodModel.Password,
|
||||
DomainLayerAuthenticationMethodModel.Pattern,
|
||||
)
|
||||
}
|
||||
|
||||
private fun FakeAuthenticationRepository.setAuthenticationMethod(
|
||||
model: DomainLayerAuthenticationMethodModel,
|
||||
) {
|
||||
setAuthenticationMethod(
|
||||
when (model) {
|
||||
is DomainLayerAuthenticationMethodModel.None,
|
||||
is DomainLayerAuthenticationMethodModel.Swipe ->
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
is DomainLayerAuthenticationMethodModel.Pin ->
|
||||
DataLayerAuthenticationMethodModel.Pin
|
||||
is DomainLayerAuthenticationMethodModel.Password ->
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
is DomainLayerAuthenticationMethodModel.Pattern ->
|
||||
DataLayerAuthenticationMethodModel.Pattern
|
||||
}
|
||||
)
|
||||
setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -55,6 +55,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
private val bouncerViewModel =
|
||||
utils.bouncerViewModel(
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
)
|
||||
private val underTest =
|
||||
PasswordBouncerViewModel(
|
||||
|
||||
@@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -57,6 +57,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
private val bouncerViewModel =
|
||||
utils.bouncerViewModel(
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
)
|
||||
private val underTest =
|
||||
PatternBouncerViewModel(
|
||||
|
||||
@@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -57,6 +57,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
private val bouncerViewModel =
|
||||
utils.bouncerViewModel(
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
)
|
||||
private val underTest =
|
||||
PinBouncerViewModel(
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.keyguard.domain.interactor
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
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.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
|
||||
private val utils = SceneTestUtils(this)
|
||||
private val testScope = utils.testScope
|
||||
private val sceneInteractor = utils.sceneInteractor()
|
||||
private val authenticationInteractor =
|
||||
utils.authenticationInteractor(
|
||||
repository = utils.authenticationRepository(),
|
||||
)
|
||||
private val underTest =
|
||||
utils.lockScreenSceneInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun isDeviceLocked() =
|
||||
testScope.runTest {
|
||||
val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
|
||||
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
assertThat(isDeviceLocked).isTrue()
|
||||
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
assertThat(isDeviceLocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
|
||||
testScope.runTest {
|
||||
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
|
||||
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
|
||||
assertThat(isSwipeToDismissEnabled).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
|
||||
testScope.runTest {
|
||||
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
|
||||
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
|
||||
assertThat(isSwipeToDismissEnabled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dismissLockScreen_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen), "reason")
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
|
||||
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
runCurrent()
|
||||
sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
runCurrent()
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
|
||||
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
runCurrent()
|
||||
sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
|
||||
runCurrent()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
@@ -49,14 +49,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
private val underTest =
|
||||
LockscreenSceneViewModel(
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor =
|
||||
utils.lockScreenSceneInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -87,17 +84,18 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
|
||||
fun upTransitionSceneKey_swipeToUnlockEnabled_gone() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
|
||||
fun upTransitionSceneKey_swipeToUnlockNotEnabled_bouncer() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.systemui.qs.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -46,14 +46,10 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||
|
||||
private val underTest =
|
||||
QuickSettingsSceneViewModel(
|
||||
lockscreenSceneInteractor =
|
||||
utils.lockScreenSceneInteractor(
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.android.systemui.scene.domain.startable
|
||||
import android.view.Display
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.shared.model.WakeSleepReason
|
||||
@@ -244,40 +245,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isDeviceUnlocked = true,
|
||||
initialSceneKey = SceneKey.Shade,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(ASLEEP)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isDeviceUnlocked = true,
|
||||
initialSceneKey = SceneKey.Shade,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(ASLEEP)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
|
||||
testScope.runTest {
|
||||
@@ -290,7 +257,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(ASLEEP)
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
@@ -307,7 +274,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(ASLEEP)
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
}
|
||||
@@ -334,22 +301,86 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.None,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.Pin,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.None,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
private fun prepareState(
|
||||
isFeatureEnabled: Boolean = true,
|
||||
isDeviceUnlocked: Boolean = false,
|
||||
isBypassEnabled: Boolean = false,
|
||||
initialSceneKey: SceneKey? = null,
|
||||
authenticationMethod: AuthenticationMethodModel? = null,
|
||||
) {
|
||||
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
|
||||
authenticationRepository.setUnlocked(isDeviceUnlocked)
|
||||
keyguardRepository.setBypassEnabled(isBypassEnabled)
|
||||
initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") }
|
||||
authenticationMethod?.let {
|
||||
authenticationRepository.setAuthenticationMethod(authenticationMethod)
|
||||
authenticationRepository.setLockscreenEnabled(
|
||||
authenticationMethod != AuthenticationMethodModel.None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ASLEEP =
|
||||
private val STARTING_TO_SLEEP =
|
||||
WakefulnessModel(
|
||||
state = WakefulnessState.ASLEEP,
|
||||
state = WakefulnessState.STARTING_TO_SLEEP,
|
||||
lastWakeReason = WakeSleepReason.POWER_BUTTON,
|
||||
lastSleepReason = WakeSleepReason.POWER_BUTTON
|
||||
)
|
||||
private val STARTING_TO_WAKE =
|
||||
WakefulnessModel(
|
||||
state = WakefulnessState.STARTING_TO_WAKE,
|
||||
lastWakeReason = WakeSleepReason.POWER_BUTTON,
|
||||
lastSleepReason = WakeSleepReason.POWER_BUTTON
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.systemui.shade.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -47,14 +47,11 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
private val underTest =
|
||||
ShadeSceneViewModel(
|
||||
applicationScope = testScope.backgroundScope,
|
||||
lockscreenSceneInteractor =
|
||||
utils.lockScreenSceneInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
sceneInteractor = sceneInteractor,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.internal.widget.LockPatternView
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -47,7 +48,7 @@ class FakeAuthenticationRepository(
|
||||
|
||||
private val _authenticationMethod =
|
||||
MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
|
||||
val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||
override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||
_authenticationMethod.asStateFlow()
|
||||
|
||||
private var isLockscreenEnabled = true
|
||||
@@ -154,13 +155,13 @@ class FakeAuthenticationRepository(
|
||||
val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
|
||||
val PATTERN =
|
||||
listOf(
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
|
||||
AuthenticationPatternCoordinate(2, 0),
|
||||
AuthenticationPatternCoordinate(2, 1),
|
||||
AuthenticationPatternCoordinate(2, 2),
|
||||
AuthenticationPatternCoordinate(1, 1),
|
||||
AuthenticationPatternCoordinate(0, 0),
|
||||
AuthenticationPatternCoordinate(0, 1),
|
||||
AuthenticationPatternCoordinate(0, 2),
|
||||
)
|
||||
const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
|
||||
const val THROTTLE_DURATION_MS = 30000
|
||||
@@ -172,7 +173,6 @@ class FakeAuthenticationRepository(
|
||||
is AuthenticationMethodModel.Pin -> SecurityMode.PIN
|
||||
is AuthenticationMethodModel.Password -> SecurityMode.Password
|
||||
is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
|
||||
is AuthenticationMethodModel.Swipe,
|
||||
is AuthenticationMethodModel.None -> SecurityMode.None
|
||||
}
|
||||
}
|
||||
@@ -208,8 +208,7 @@ class FakeAuthenticationRepository(
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
|
||||
List<LockPatternView.Cell> {
|
||||
private fun List<AuthenticationPatternCoordinate>.toCells(): List<LockPatternView.Cell> {
|
||||
return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import com.android.systemui.keyguard.data.repository.FakeCommandQueue
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import com.android.systemui.keyguard.shared.model.WakeSleepReason
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessState
|
||||
@@ -176,23 +175,14 @@ class SceneTestUtils(
|
||||
|
||||
fun bouncerViewModel(
|
||||
bouncerInteractor: BouncerInteractor,
|
||||
authenticationInteractor: AuthenticationInteractor,
|
||||
): BouncerViewModel {
|
||||
return BouncerViewModel(
|
||||
applicationContext = context,
|
||||
applicationScope = applicationScope(),
|
||||
interactor = bouncerInteractor,
|
||||
featureFlags = featureFlags,
|
||||
)
|
||||
}
|
||||
|
||||
fun lockScreenSceneInteractor(
|
||||
authenticationInteractor: AuthenticationInteractor,
|
||||
bouncerInteractor: BouncerInteractor,
|
||||
): LockscreenSceneInteractor {
|
||||
return LockscreenSceneInteractor(
|
||||
applicationScope = applicationScope(),
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
featureFlags = featureFlags,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user