Merge changes Ia719fce3,If1a64a93,I90b025f5,I54449898,I32b7b585 into udc-dev
* changes: [flexiglass] Foundation for placeholder QS scene. [flexiglass] Foundation for placeholder shade scene. [flexiglass] Foundation for placeholder lock screen scene. [flexiglass] Foundation for placeholder bouncer scene. [flexiglass] Authentication logic.
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.authentication
|
||||||
|
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule
|
||||||
|
import dagger.Module
|
||||||
|
|
||||||
|
@Module(
|
||||||
|
includes =
|
||||||
|
[
|
||||||
|
AuthenticationRepositoryModule::class,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
object AuthenticationModule
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.authentication.data.repository
|
||||||
|
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/** Defines interface for classes that can access authentication-related application state. */
|
||||||
|
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 lock screen is showing or dismissed.
|
||||||
|
*/
|
||||||
|
val isUnlocked: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently-configured authentication method. This determines how the authentication
|
||||||
|
* challenge is completed in order to unlock an otherwise locked device.
|
||||||
|
*/
|
||||||
|
val authenticationMethod: StateFlow<AuthenticationMethodModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
|
||||||
|
* dismisses once the authentication challenge is completed. For example, completing a biometric
|
||||||
|
* authentication challenge via face unlock or fingerprint sensor can automatically bypass the
|
||||||
|
* lock screen.
|
||||||
|
*/
|
||||||
|
val isBypassEnabled: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/** See [isUnlocked]. */
|
||||||
|
fun setUnlocked(isUnlocked: Boolean)
|
||||||
|
|
||||||
|
/** See [authenticationMethod]. */
|
||||||
|
fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel)
|
||||||
|
|
||||||
|
/** See [isBypassEnabled]. */
|
||||||
|
fun setBypassEnabled(isBypassEnabled: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
|
||||||
|
// TODO(b/280883900): get data from real data sources in SysUI.
|
||||||
|
|
||||||
|
private val _isUnlocked = MutableStateFlow(false)
|
||||||
|
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
|
||||||
|
|
||||||
|
private val _authenticationMethod =
|
||||||
|
MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234))
|
||||||
|
override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||||
|
_authenticationMethod.asStateFlow()
|
||||||
|
|
||||||
|
private val _isBypassEnabled = MutableStateFlow(false)
|
||||||
|
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
|
||||||
|
|
||||||
|
override fun setUnlocked(isUnlocked: Boolean) {
|
||||||
|
_isUnlocked.value = isUnlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBypassEnabled(isBypassEnabled: Boolean) {
|
||||||
|
_isBypassEnabled.value = isBypassEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
|
||||||
|
_authenticationMethod.value = authenticationMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module
|
||||||
|
interface AuthenticationRepositoryModule {
|
||||||
|
@Binds fun repository(impl: AuthenticationRepositoryImpl): AuthenticationRepository
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* 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.authentication.domain.interactor
|
||||||
|
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
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.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** Hosts application business logic related to authentication. */
|
||||||
|
@SysUISingleton
|
||||||
|
class AuthenticationInteractor
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@Application applicationScope: CoroutineScope,
|
||||||
|
private val repository: AuthenticationRepository,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The currently-configured authentication method. This determines how the authentication
|
||||||
|
* challenge is completed in order to unlock an otherwise locked device.
|
||||||
|
*/
|
||||||
|
val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
val isUnlocked: StateFlow<Boolean> =
|
||||||
|
combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked ->
|
||||||
|
isUnlockedWithAuthMethod(
|
||||||
|
isUnlocked = isUnlocked,
|
||||||
|
authMethod = authMethod,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = applicationScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue =
|
||||||
|
isUnlockedWithAuthMethod(
|
||||||
|
isUnlocked = repository.isUnlocked.value,
|
||||||
|
authMethod = repository.authenticationMethod.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
|
||||||
|
* dismisses once the authentication challenge is completed. For example, completing a biometric
|
||||||
|
* authentication challenge via face unlock or fingerprint sensor can automatically bypass the
|
||||||
|
* lock screen.
|
||||||
|
*/
|
||||||
|
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
|
||||||
|
|
||||||
|
init {
|
||||||
|
// UNLOCKS WHEN AUTH METHOD REMOVED.
|
||||||
|
//
|
||||||
|
// Unlocks the device if the auth method becomes None.
|
||||||
|
applicationScope.launch {
|
||||||
|
repository.authenticationMethod.collect {
|
||||||
|
if (it is AuthenticationMethodModel.None) {
|
||||||
|
unlockDevice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the device currently requires authentication before content can be viewed;
|
||||||
|
* `false` if content can be displayed without unlocking first.
|
||||||
|
*/
|
||||||
|
fun isAuthenticationRequired(): Boolean {
|
||||||
|
return !isUnlocked.value && authenticationMethod.value.isSecure
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlocks the device, assuming that the authentication challenge has been completed
|
||||||
|
* successfully.
|
||||||
|
*/
|
||||||
|
fun unlockDevice() {
|
||||||
|
repository.setUnlocked(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks the device. From now on, the device will remain locked until [authenticate] is called
|
||||||
|
* with the correct input.
|
||||||
|
*/
|
||||||
|
fun lockDevice() {
|
||||||
|
repository.setUnlocked(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to authenticate the user and unlock the device.
|
||||||
|
*
|
||||||
|
* @param input The input from the user to try to authenticate with. This can be a list of
|
||||||
|
* different things, based on the current authentication method.
|
||||||
|
* @return `true` if the authentication succeeded and the device is now unlocked; `false`
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
fun authenticate(input: List<Any>): Boolean {
|
||||||
|
val isSuccessful =
|
||||||
|
when (val authMethod = this.authenticationMethod.value) {
|
||||||
|
is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code
|
||||||
|
is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
|
||||||
|
is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccessful) {
|
||||||
|
repository.setUnlocked(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Triggers a biometric-powered unlock of the device. */
|
||||||
|
fun biometricUnlock() {
|
||||||
|
// TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
|
||||||
|
repository.setUnlocked(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** See [authenticationMethod]. */
|
||||||
|
fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
|
||||||
|
repository.setAuthenticationMethod(authenticationMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** See [isBypassEnabled]. */
|
||||||
|
fun toggleBypassEnabled() {
|
||||||
|
repository.setBypassEnabled(!repository.isBypassEnabled.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun isUnlockedWithAuthMethod(
|
||||||
|
isUnlocked: Boolean,
|
||||||
|
authMethod: AuthenticationMethodModel,
|
||||||
|
): Boolean {
|
||||||
|
return if (authMethod is AuthenticationMethodModel.None) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
isUnlocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a PIN code from the given list. It's assumed the given list elements are all
|
||||||
|
* [Int].
|
||||||
|
*/
|
||||||
|
private fun List<Any>.asCode(): Int? {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = 0
|
||||||
|
map { it as Int }.forEach { integer -> code = code * 10 + integer }
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a password from the given list. It's assumed the given list elements are all
|
||||||
|
* [Char].
|
||||||
|
*/
|
||||||
|
private fun List<Any>.asPassword(): String {
|
||||||
|
val anyList = this
|
||||||
|
return buildString { anyList.forEach { append(it as Char) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
|
||||||
|
* list. It's assumed the given list elements are all
|
||||||
|
* [AuthenticationMethodModel.Pattern.PatternCoordinate].
|
||||||
|
*/
|
||||||
|
private fun List<Any>.asPattern():
|
||||||
|
List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
|
||||||
|
val anyList = this
|
||||||
|
return buildList {
|
||||||
|
anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.authentication.shared.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)
|
||||||
|
|
||||||
|
/** The most basic authentication method. The lock screen can be swiped away when displayed. */
|
||||||
|
object Swipe : AuthenticationMethodModel(isSecure = false)
|
||||||
|
|
||||||
|
data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true)
|
||||||
|
|
||||||
|
data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
|
||||||
|
|
||||||
|
data class Pattern(val coordinates: List<PatternCoordinate>) :
|
||||||
|
AuthenticationMethodModel(isSecure = true) {
|
||||||
|
|
||||||
|
data class PatternCoordinate(
|
||||||
|
val x: Int,
|
||||||
|
val y: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.bouncer.data.repo
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/** Provides access to bouncer-related application state. */
|
||||||
|
@SysUISingleton
|
||||||
|
class BouncerRepository @Inject constructor() {
|
||||||
|
private val _message = MutableStateFlow<String?>(null)
|
||||||
|
/** The user-facing message to show in the bouncer. */
|
||||||
|
val message: StateFlow<String?> = _message.asStateFlow()
|
||||||
|
|
||||||
|
fun setMessage(message: String?) {
|
||||||
|
_message.value = message
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
import com.android.systemui.scene.shared.model.SceneKey
|
||||||
|
import com.android.systemui.scene.shared.model.SceneModel
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** Encapsulates business logic and application state accessing use-cases. */
|
||||||
|
class BouncerInteractor
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
@Application private val applicationScope: CoroutineScope,
|
||||||
|
@Application private val applicationContext: Context,
|
||||||
|
private val repository: BouncerRepository,
|
||||||
|
private val authenticationInteractor: AuthenticationInteractor,
|
||||||
|
private val sceneInteractor: SceneInteractor,
|
||||||
|
@Assisted private val containerName: String,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** The user-facing message to show in the bouncer. */
|
||||||
|
val message: StateFlow<String?> = repository.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently-configured authentication method. This determines how the authentication
|
||||||
|
* challenge is completed in order to unlock an otherwise locked device.
|
||||||
|
*/
|
||||||
|
val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||||
|
authenticationInteractor.authenticationMethod
|
||||||
|
|
||||||
|
init {
|
||||||
|
applicationScope.launch {
|
||||||
|
combine(
|
||||||
|
sceneInteractor.currentScene(containerName),
|
||||||
|
authenticationInteractor.authenticationMethod,
|
||||||
|
::Pair,
|
||||||
|
)
|
||||||
|
.collect { (currentScene, authMethod) ->
|
||||||
|
if (currentScene.key == SceneKey.Bouncer) {
|
||||||
|
when (authMethod) {
|
||||||
|
is AuthenticationMethodModel.None ->
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName,
|
||||||
|
SceneModel(SceneKey.Gone),
|
||||||
|
)
|
||||||
|
is AuthenticationMethodModel.Swipe ->
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName,
|
||||||
|
SceneModel(SceneKey.LockScreen),
|
||||||
|
)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
|
||||||
|
*
|
||||||
|
* @param containerName The name of the scene container to show the bouncer in.
|
||||||
|
* @param message An optional message to show to the user in the bouncer.
|
||||||
|
*/
|
||||||
|
fun showOrUnlockDevice(
|
||||||
|
containerName: String,
|
||||||
|
message: String? = null,
|
||||||
|
) {
|
||||||
|
if (authenticationInteractor.isAuthenticationRequired()) {
|
||||||
|
repository.setMessage(message ?: promptMessage(authenticationMethod.value))
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.Bouncer),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
authenticationInteractor.unlockDevice()
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.Gone),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the user-facing message back to the default according to the current authentication
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
fun resetMessage() {
|
||||||
|
repository.setMessage(promptMessage(authenticationMethod.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes the user-facing message. */
|
||||||
|
fun clearMessage() {
|
||||||
|
repository.setMessage(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to authenticate based on the given user input.
|
||||||
|
*
|
||||||
|
* If the input is correct, the device will be unlocked and the lock screen and bouncer will be
|
||||||
|
* dismissed and hidden.
|
||||||
|
*/
|
||||||
|
fun authenticate(
|
||||||
|
input: List<Any>,
|
||||||
|
) {
|
||||||
|
val isAuthenticated = authenticationInteractor.authenticate(input)
|
||||||
|
if (isAuthenticated) {
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.Gone),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
repository.setMessage(errorMessage(authenticationMethod.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun promptMessage(authMethod: AuthenticationMethodModel): String {
|
||||||
|
return when (authMethod) {
|
||||||
|
is AuthenticationMethodModel.PIN ->
|
||||||
|
applicationContext.getString(R.string.keyguard_enter_your_pin)
|
||||||
|
is AuthenticationMethodModel.Password ->
|
||||||
|
applicationContext.getString(R.string.keyguard_enter_your_password)
|
||||||
|
is AuthenticationMethodModel.Pattern ->
|
||||||
|
applicationContext.getString(R.string.keyguard_enter_your_pattern)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun errorMessage(authMethod: AuthenticationMethodModel): String {
|
||||||
|
return when (authMethod) {
|
||||||
|
is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin)
|
||||||
|
is AuthenticationMethodModel.Password ->
|
||||||
|
applicationContext.getString(R.string.kg_wrong_password)
|
||||||
|
is AuthenticationMethodModel.Pattern ->
|
||||||
|
applicationContext.getString(R.string.kg_wrong_pattern)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
containerName: String,
|
||||||
|
): BouncerInteractor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.bouncer.ui.viewmodel
|
||||||
|
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/** Holds UI state and handles user input on bouncer UIs. */
|
||||||
|
class BouncerViewModel
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
@Application private val applicationScope: CoroutineScope,
|
||||||
|
interactorFactory: BouncerInteractor.Factory,
|
||||||
|
containerName: String,
|
||||||
|
) {
|
||||||
|
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
|
||||||
|
|
||||||
|
/** The user-facing message to show in the bouncer. */
|
||||||
|
val message: StateFlow<String> =
|
||||||
|
interactor.message
|
||||||
|
.map { it ?: "" }
|
||||||
|
.stateIn(
|
||||||
|
scope = applicationScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue = interactor.message.value ?: "",
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Notifies that the authenticate button was clicked. */
|
||||||
|
fun onAuthenticateButtonClicked() {
|
||||||
|
// TODO(b/280877228): remove this and send the real input.
|
||||||
|
interactor.authenticate(
|
||||||
|
when (interactor.authenticationMethod.value) {
|
||||||
|
is AuthenticationMethodModel.PIN -> listOf(1, 2, 3, 4)
|
||||||
|
is AuthenticationMethodModel.Password -> "password".toList()
|
||||||
|
is AuthenticationMethodModel.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),
|
||||||
|
)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Notifies that the emergency services button was clicked. */
|
||||||
|
fun onEmergencyServicesButtonClicked() {
|
||||||
|
// TODO(b/280877228): implement this.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import com.android.systemui.accessibility.AccessibilityModule;
|
|||||||
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
|
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
|
||||||
import com.android.systemui.appops.dagger.AppOpsModule;
|
import com.android.systemui.appops.dagger.AppOpsModule;
|
||||||
import com.android.systemui.assist.AssistModule;
|
import com.android.systemui.assist.AssistModule;
|
||||||
|
import com.android.systemui.authentication.AuthenticationModule;
|
||||||
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
|
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
|
||||||
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
|
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
|
||||||
import com.android.systemui.biometrics.FingerprintReEnrollNotification;
|
import com.android.systemui.biometrics.FingerprintReEnrollNotification;
|
||||||
@@ -153,6 +154,7 @@ import javax.inject.Named;
|
|||||||
AccessibilityRepositoryModule.class,
|
AccessibilityRepositoryModule.class,
|
||||||
AppOpsModule.class,
|
AppOpsModule.class,
|
||||||
AssistModule.class,
|
AssistModule.class,
|
||||||
|
AuthenticationModule.class,
|
||||||
BiometricsModule.class,
|
BiometricsModule.class,
|
||||||
BouncerViewModule.class,
|
BouncerViewModule.class,
|
||||||
ClipboardOverlayModule.class,
|
ClipboardOverlayModule.class,
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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.qualifiers.Application
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
import com.android.systemui.scene.shared.model.SceneKey
|
||||||
|
import com.android.systemui.scene.shared.model.SceneModel
|
||||||
|
import com.android.systemui.util.kotlin.pairwise
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
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
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** Hosts business and application state accessing logic for the lock screen scene. */
|
||||||
|
class LockScreenSceneInteractor
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
@Application applicationScope: CoroutineScope,
|
||||||
|
private val authenticationInteractor: AuthenticationInteractor,
|
||||||
|
bouncerInteractorFactory: BouncerInteractor.Factory,
|
||||||
|
private val sceneInteractor: SceneInteractor,
|
||||||
|
@Assisted private val containerName: String,
|
||||||
|
) {
|
||||||
|
private val bouncerInteractor: BouncerInteractor =
|
||||||
|
bouncerInteractorFactory.create(containerName)
|
||||||
|
|
||||||
|
/** 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 lock screen. */
|
||||||
|
val isSwipeToDismissEnabled: StateFlow<Boolean> =
|
||||||
|
combine(
|
||||||
|
authenticationInteractor.isUnlocked,
|
||||||
|
authenticationInteractor.authenticationMethod,
|
||||||
|
) { isUnlocked, authMethod ->
|
||||||
|
isSwipeToUnlockEnabled(
|
||||||
|
isUnlocked = isUnlocked,
|
||||||
|
authMethod = authMethod,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = applicationScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue =
|
||||||
|
isSwipeToUnlockEnabled(
|
||||||
|
isUnlocked = authenticationInteractor.isUnlocked.value,
|
||||||
|
authMethod = authenticationInteractor.authenticationMethod.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
// LOCKING SHOWS LOCK SCREEN.
|
||||||
|
//
|
||||||
|
// Move to the lock screen scene if the device becomes locked while in any scene.
|
||||||
|
applicationScope.launch {
|
||||||
|
authenticationInteractor.isUnlocked
|
||||||
|
.map { !it }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { isLocked ->
|
||||||
|
if (isLocked) {
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.LockScreen),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BYPASS UNLOCK.
|
||||||
|
//
|
||||||
|
// Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
|
||||||
|
// lock screen scene.
|
||||||
|
applicationScope.launch {
|
||||||
|
combine(
|
||||||
|
authenticationInteractor.isBypassEnabled,
|
||||||
|
authenticationInteractor.isUnlocked,
|
||||||
|
sceneInteractor.currentScene(containerName),
|
||||||
|
::Triple,
|
||||||
|
)
|
||||||
|
.collect { (isBypassEnabled, isUnlocked, currentScene) ->
|
||||||
|
if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.LockScreen) {
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.Gone),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SWIPE TO DISMISS LOCK SCREEN.
|
||||||
|
//
|
||||||
|
// If switched from the lock screen to the gone scene and the auth method was a swipe,
|
||||||
|
// unlocks the device.
|
||||||
|
applicationScope.launch {
|
||||||
|
combine(
|
||||||
|
authenticationInteractor.authenticationMethod,
|
||||||
|
sceneInteractor.currentScene(containerName).pairwise(),
|
||||||
|
::Pair,
|
||||||
|
)
|
||||||
|
.collect { (authMethod, scenes) ->
|
||||||
|
val (previousScene, currentScene) = scenes
|
||||||
|
if (
|
||||||
|
authMethod is AuthenticationMethodModel.Swipe &&
|
||||||
|
previousScene.key == SceneKey.LockScreen &&
|
||||||
|
currentScene.key == SceneKey.Gone
|
||||||
|
) {
|
||||||
|
authenticationInteractor.unlockDevice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DISMISS LOCK SCREEN IF AUTH METHOD IS REMOVED.
|
||||||
|
//
|
||||||
|
// If the auth method becomes None while on the lock screen scene, dismisses the lock
|
||||||
|
// screen.
|
||||||
|
applicationScope.launch {
|
||||||
|
combine(
|
||||||
|
authenticationInteractor.authenticationMethod,
|
||||||
|
sceneInteractor.currentScene(containerName),
|
||||||
|
::Pair,
|
||||||
|
)
|
||||||
|
.collect { (authMethod, scene) ->
|
||||||
|
if (
|
||||||
|
scene.key == SceneKey.LockScreen &&
|
||||||
|
authMethod == AuthenticationMethodModel.None
|
||||||
|
) {
|
||||||
|
sceneInteractor.setCurrentScene(
|
||||||
|
containerName = containerName,
|
||||||
|
scene = SceneModel(SceneKey.Gone),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempts to dismiss the lock screen. This will cause the bouncer to show, if needed. */
|
||||||
|
fun dismissLockScreen() {
|
||||||
|
bouncerInteractor.showOrUnlockDevice(containerName = containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSwipeToUnlockEnabled(
|
||||||
|
isUnlocked: Boolean,
|
||||||
|
authMethod: AuthenticationMethodModel,
|
||||||
|
): Boolean {
|
||||||
|
return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
containerName: String,
|
||||||
|
): LockScreenSceneInteractor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.viewmodel
|
||||||
|
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.common.shared.model.ContentDescription
|
||||||
|
import com.android.systemui.common.shared.model.Icon
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import com.android.systemui.scene.shared.model.SceneKey
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/** Models UI state and handles user input for the lock screen scene. */
|
||||||
|
class LockScreenSceneViewModel
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
@Application applicationScope: CoroutineScope,
|
||||||
|
interactorFactory: LockScreenSceneInteractor.Factory,
|
||||||
|
@Assisted containerName: String,
|
||||||
|
) {
|
||||||
|
private val interactor: LockScreenSceneInteractor = interactorFactory.create(containerName)
|
||||||
|
|
||||||
|
/** The icon for the "lock" button on the lock screen. */
|
||||||
|
val lockButtonIcon: StateFlow<Icon> =
|
||||||
|
interactor.isDeviceLocked
|
||||||
|
.map { isLocked -> lockIcon(isLocked = isLocked) }
|
||||||
|
.stateIn(
|
||||||
|
scope = applicationScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue = lockIcon(isLocked = interactor.isDeviceLocked.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),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Notifies that the lock button on the lock screen was clicked. */
|
||||||
|
fun onLockButtonClicked() {
|
||||||
|
interactor.dismissLockScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Notifies that some content on the lock screen was clicked. */
|
||||||
|
fun onContentClicked() {
|
||||||
|
interactor.dismissLockScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upDestinationSceneKey(
|
||||||
|
isSwipeToUnlockEnabled: Boolean,
|
||||||
|
): SceneKey {
|
||||||
|
return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lockIcon(
|
||||||
|
isLocked: Boolean,
|
||||||
|
): Icon {
|
||||||
|
return Icon.Resource(
|
||||||
|
res =
|
||||||
|
if (isLocked) {
|
||||||
|
R.drawable.ic_device_lock_on
|
||||||
|
} else {
|
||||||
|
R.drawable.ic_device_lock_off
|
||||||
|
},
|
||||||
|
contentDescription =
|
||||||
|
ContentDescription.Resource(
|
||||||
|
res =
|
||||||
|
if (isLocked) {
|
||||||
|
R.string.accessibility_lock_icon
|
||||||
|
} else {
|
||||||
|
R.string.accessibility_unlock_button
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
containerName: String,
|
||||||
|
): LockScreenSceneViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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.qs.ui.viewmodel
|
||||||
|
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
/** Models UI state and handles user input for the quick settings scene. */
|
||||||
|
class QuickSettingsSceneViewModel
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
|
||||||
|
@Assisted containerName: String,
|
||||||
|
) {
|
||||||
|
private val lockScreenSceneInteractor: LockScreenSceneInteractor =
|
||||||
|
lockScreenSceneInteractorFactory.create(containerName)
|
||||||
|
|
||||||
|
/** Notifies that some content in quick settings was clicked. */
|
||||||
|
fun onContentClicked() {
|
||||||
|
lockScreenSceneInteractor.dismissLockScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
containerName: String,
|
||||||
|
): QuickSettingsSceneViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.shade.ui.viewmodel
|
||||||
|
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import com.android.systemui.scene.shared.model.SceneKey
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/** Models UI state and handles user input for the shade scene. */
|
||||||
|
class ShadeSceneViewModel
|
||||||
|
@AssistedInject
|
||||||
|
constructor(
|
||||||
|
@Application private val applicationScope: CoroutineScope,
|
||||||
|
lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
|
||||||
|
@Assisted private val containerName: String,
|
||||||
|
) {
|
||||||
|
private val lockScreenInteractor: LockScreenSceneInteractor =
|
||||||
|
lockScreenSceneInteractorFactory.create(containerName)
|
||||||
|
|
||||||
|
/** The key of the scene we should switch to when swiping up. */
|
||||||
|
val upDestinationSceneKey: StateFlow<SceneKey> =
|
||||||
|
lockScreenInteractor.isDeviceLocked
|
||||||
|
.map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
|
||||||
|
.stateIn(
|
||||||
|
scope = applicationScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue =
|
||||||
|
upDestinationSceneKey(
|
||||||
|
isLocked = lockScreenInteractor.isDeviceLocked.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Notifies that some content in the shade was clicked. */
|
||||||
|
fun onContentClicked() {
|
||||||
|
lockScreenInteractor.dismissLockScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upDestinationSceneKey(
|
||||||
|
isLocked: Boolean,
|
||||||
|
): SceneKey {
|
||||||
|
return if (isLocked) SceneKey.LockScreen else SceneKey.Gone
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
containerName: String,
|
||||||
|
): ShadeSceneViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
/*
|
||||||
|
* 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.authentication.domain.interactor
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
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 AuthenticationInteractorTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val testScope = TestScope()
|
||||||
|
private val repository: AuthenticationRepository = AuthenticationRepositoryImpl()
|
||||||
|
private val underTest =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = repository,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authMethod() =
|
||||||
|
testScope.runTest {
|
||||||
|
val authMethod by collectLastValue(underTest.authenticationMethod)
|
||||||
|
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234))
|
||||||
|
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||||
|
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isUnlocked_whenAuthMethodIsNone_isTrue() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
underTest.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun biometricUnlock() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
underTest.biometricUnlock()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun toggleBypassEnabled() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
|
||||||
|
assertThat(isBypassEnabled).isFalse()
|
||||||
|
|
||||||
|
underTest.toggleBypassEnabled()
|
||||||
|
assertThat(isBypassEnabled).isTrue()
|
||||||
|
|
||||||
|
underTest.toggleBypassEnabled()
|
||||||
|
assertThat(isBypassEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isAuthenticationRequired_lockedAndSecured_true() =
|
||||||
|
testScope.runTest {
|
||||||
|
underTest.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||||
|
|
||||||
|
assertThat(underTest.isAuthenticationRequired()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isAuthenticationRequired_lockedAndNotSecured_false() =
|
||||||
|
testScope.runTest {
|
||||||
|
underTest.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
|
||||||
|
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isAuthenticationRequired_unlockedAndSecured_false() =
|
||||||
|
testScope.runTest {
|
||||||
|
underTest.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||||
|
|
||||||
|
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
|
||||||
|
testScope.runTest {
|
||||||
|
underTest.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
|
||||||
|
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(underTest.authenticate("password".toList())).isTrue()
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Pattern(
|
||||||
|
listOf(
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 1,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
underTest.authenticate(
|
||||||
|
listOf(
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 1,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.isTrue()
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
underTest.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Pattern(
|
||||||
|
listOf(
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 1,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 0,
|
||||||
|
y = 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
underTest.authenticate(
|
||||||
|
listOf(
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 2,
|
||||||
|
y = 0,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 2,
|
||||||
|
y = 1,
|
||||||
|
),
|
||||||
|
AuthenticationMethodModel.Pattern.PatternCoordinate(
|
||||||
|
x = 2,
|
||||||
|
y = 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.isFalse()
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unlocksDevice_whenAuthMethodBecomesNone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
repository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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.bouncer.domain.interactor
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
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.TestScope
|
||||||
|
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
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class BouncerInteractorTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val testScope = TestScope()
|
||||||
|
private val authenticationInteractor =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = AuthenticationRepositoryImpl(),
|
||||||
|
)
|
||||||
|
private val sceneInteractor =
|
||||||
|
SceneInteractor(
|
||||||
|
repository = fakeSceneContainerRepository(),
|
||||||
|
)
|
||||||
|
private val underTest =
|
||||||
|
BouncerInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
applicationContext = context,
|
||||||
|
repository = BouncerRepository(),
|
||||||
|
authenticationInteractor = authenticationInteractor,
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = "container1",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
|
||||||
|
overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
|
||||||
|
overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
|
||||||
|
overrideResource(R.string.kg_wrong_pin, MESSAGE_WRONG_PIN)
|
||||||
|
overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
|
||||||
|
overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun pinAuthMethod() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
val message by collectLastValue(underTest.message)
|
||||||
|
|
||||||
|
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
authenticationInteractor.lockDevice()
|
||||||
|
underTest.showOrUnlockDevice("container1")
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||||
|
|
||||||
|
underTest.clearMessage()
|
||||||
|
assertThat(message).isNull()
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||||
|
|
||||||
|
// Wrong input.
|
||||||
|
underTest.authenticate(listOf(9, 8, 7))
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||||
|
|
||||||
|
// Correct input.
|
||||||
|
underTest.authenticate(listOf(1, 2, 3, 4))
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun passwordAuthMethod() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
val message by collectLastValue(underTest.message)
|
||||||
|
authenticationInteractor.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Password("password")
|
||||||
|
)
|
||||||
|
authenticationInteractor.lockDevice()
|
||||||
|
underTest.showOrUnlockDevice("container1")
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
|
||||||
|
|
||||||
|
underTest.clearMessage()
|
||||||
|
assertThat(message).isNull()
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
|
||||||
|
|
||||||
|
// Wrong input.
|
||||||
|
underTest.authenticate("alohamora".toList())
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
|
||||||
|
|
||||||
|
// Correct input.
|
||||||
|
underTest.authenticate("password".toList())
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun patternAuthMethod() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
val message by collectLastValue(underTest.message)
|
||||||
|
authenticationInteractor.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Pattern(emptyList())
|
||||||
|
)
|
||||||
|
authenticationInteractor.lockDevice()
|
||||||
|
underTest.showOrUnlockDevice("container1")
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||||
|
|
||||||
|
underTest.clearMessage()
|
||||||
|
assertThat(message).isNull()
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||||
|
|
||||||
|
// Wrong input.
|
||||||
|
underTest.authenticate(
|
||||||
|
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
|
||||||
|
)
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
|
||||||
|
underTest.resetMessage()
|
||||||
|
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||||
|
|
||||||
|
// Correct input.
|
||||||
|
underTest.authenticate(emptyList())
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
authenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.showOrUnlockDevice("container1")
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
authenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
underTest.showOrUnlockDevice("container1")
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun showOrUnlockDevice_customMessageShown() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||||
|
val message by collectLastValue(underTest.message)
|
||||||
|
authenticationInteractor.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Password("password")
|
||||||
|
)
|
||||||
|
authenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
val customMessage = "Hello there!"
|
||||||
|
underTest.showOrUnlockDevice("container1", customMessage)
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
assertThat(message).isEqualTo(customMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
|
||||||
|
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
|
||||||
|
private const val MESSAGE_ENTER_YOUR_PATTERN = "Enter your pattern"
|
||||||
|
private const val MESSAGE_WRONG_PIN = "Wrong PIN"
|
||||||
|
private const val MESSAGE_WRONG_PASSWORD = "Wrong password"
|
||||||
|
private const val MESSAGE_WRONG_PATTERN = "Wrong pattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
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.TestScope
|
||||||
|
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 testScope = TestScope()
|
||||||
|
private val sceneInteractor =
|
||||||
|
SceneInteractor(
|
||||||
|
repository = fakeSceneContainerRepository(),
|
||||||
|
)
|
||||||
|
private val mAuthenticationInteractor =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = AuthenticationRepositoryImpl(),
|
||||||
|
)
|
||||||
|
private val underTest =
|
||||||
|
LockScreenSceneInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
bouncerInteractorFactory =
|
||||||
|
object : BouncerInteractor.Factory {
|
||||||
|
override fun create(containerName: String): BouncerInteractor {
|
||||||
|
return BouncerInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
applicationContext = context,
|
||||||
|
repository = BouncerRepository(),
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = containerName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = CONTAINER_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDeviceLocked() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
|
||||||
|
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
assertThat(isDeviceLocked).isTrue()
|
||||||
|
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
assertThat(isDeviceLocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
|
||||||
|
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
|
||||||
|
assertThat(isSwipeToDismissEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
|
||||||
|
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
|
||||||
|
assertThat(isSwipeToDismissEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
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(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
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(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
|
||||||
|
underTest.dismissLockScreen()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
runCurrent()
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
|
||||||
|
runCurrent()
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
|
||||||
|
if (!mAuthenticationInteractor.isBypassEnabled.value) {
|
||||||
|
mAuthenticationInteractor.toggleBypassEnabled()
|
||||||
|
}
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
|
||||||
|
mAuthenticationInteractor.biometricUnlock()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
|
||||||
|
if (mAuthenticationInteractor.isBypassEnabled.value) {
|
||||||
|
mAuthenticationInteractor.toggleBypassEnabled()
|
||||||
|
}
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
|
||||||
|
mAuthenticationInteractor.biometricUnlock()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
|
||||||
|
testScope.runTest {
|
||||||
|
val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
|
||||||
|
runCurrent()
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Shade))
|
||||||
|
runCurrent()
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
runCurrent()
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
|
||||||
|
|
||||||
|
assertThat(isUnlocked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
|
||||||
|
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
runCurrent()
|
||||||
|
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.QuickSettings))
|
||||||
|
runCurrent()
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
|
||||||
|
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTAINER_NAME = "container1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.viewmodel
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
|
import com.android.systemui.common.shared.model.Icon
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
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.TestScope
|
||||||
|
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 LockScreenSceneViewModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val testScope = TestScope()
|
||||||
|
private val sceneInteractor =
|
||||||
|
SceneInteractor(
|
||||||
|
repository = fakeSceneContainerRepository(),
|
||||||
|
)
|
||||||
|
private val mAuthenticationInteractor =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = AuthenticationRepositoryImpl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val underTest =
|
||||||
|
LockScreenSceneViewModel(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
interactorFactory =
|
||||||
|
object : LockScreenSceneInteractor.Factory {
|
||||||
|
override fun create(containerName: String): LockScreenSceneInteractor {
|
||||||
|
return LockScreenSceneInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
bouncerInteractorFactory =
|
||||||
|
object : BouncerInteractor.Factory {
|
||||||
|
override fun create(containerName: String): BouncerInteractor {
|
||||||
|
return BouncerInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
applicationContext = context,
|
||||||
|
repository = BouncerRepository(),
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = containerName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = CONTAINER_NAME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerName = CONTAINER_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lockButtonIcon_whenLocked() =
|
||||||
|
testScope.runTest {
|
||||||
|
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Password("password")
|
||||||
|
)
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
assertThat((lockButtonIcon as? Icon.Resource)?.res)
|
||||||
|
.isEqualTo(R.drawable.ic_device_lock_on)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lockButtonIcon_whenUnlocked() =
|
||||||
|
testScope.runTest {
|
||||||
|
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(
|
||||||
|
AuthenticationMethodModel.Password("password")
|
||||||
|
)
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
|
||||||
|
assertThat((lockButtonIcon as? Icon.Resource)?.res)
|
||||||
|
.isEqualTo(R.drawable.ic_device_lock_off)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onLockButtonClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onLockButtonClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTAINER_NAME = "container1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.qs.ui.viewmodel
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
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.TestScope
|
||||||
|
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 QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val testScope = TestScope()
|
||||||
|
private val sceneInteractor =
|
||||||
|
SceneInteractor(
|
||||||
|
repository = fakeSceneContainerRepository(),
|
||||||
|
)
|
||||||
|
private val mAuthenticationInteractor =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = AuthenticationRepositoryImpl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val underTest =
|
||||||
|
QuickSettingsSceneViewModel(
|
||||||
|
lockScreenSceneInteractorFactory =
|
||||||
|
object : LockScreenSceneInteractor.Factory {
|
||||||
|
override fun create(containerName: String): LockScreenSceneInteractor {
|
||||||
|
return LockScreenSceneInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
bouncerInteractorFactory =
|
||||||
|
object : BouncerInteractor.Factory {
|
||||||
|
override fun create(containerName: String): BouncerInteractor {
|
||||||
|
return BouncerInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
applicationContext = context,
|
||||||
|
repository = BouncerRepository(),
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = containerName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = CONTAINER_NAME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerName = CONTAINER_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTAINER_NAME = "container1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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.shade.ui.viewmodel
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
|
||||||
|
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||||
|
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||||
|
import com.android.systemui.bouncer.data.repo.BouncerRepository
|
||||||
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
|
||||||
|
import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
|
||||||
|
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||||
|
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.TestScope
|
||||||
|
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 ShadeSceneViewModelTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val testScope = TestScope()
|
||||||
|
private val sceneInteractor =
|
||||||
|
SceneInteractor(
|
||||||
|
repository = fakeSceneContainerRepository(),
|
||||||
|
)
|
||||||
|
private val mAuthenticationInteractor =
|
||||||
|
AuthenticationInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
repository = AuthenticationRepositoryImpl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val underTest =
|
||||||
|
ShadeSceneViewModel(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
lockScreenSceneInteractorFactory =
|
||||||
|
object : LockScreenSceneInteractor.Factory {
|
||||||
|
override fun create(containerName: String): LockScreenSceneInteractor {
|
||||||
|
return LockScreenSceneInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
bouncerInteractorFactory =
|
||||||
|
object : BouncerInteractor.Factory {
|
||||||
|
override fun create(containerName: String): BouncerInteractor {
|
||||||
|
return BouncerInteractor(
|
||||||
|
applicationScope = testScope.backgroundScope,
|
||||||
|
applicationContext = context,
|
||||||
|
repository = BouncerRepository(),
|
||||||
|
authenticationInteractor = mAuthenticationInteractor,
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = containerName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sceneInteractor = sceneInteractor,
|
||||||
|
containerName = CONTAINER_NAME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerName = CONTAINER_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun upTransitionSceneKey_deviceLocked_lockScreen() =
|
||||||
|
testScope.runTest {
|
||||||
|
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
|
||||||
|
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.LockScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun upTransitionSceneKey_deviceUnlocked_gone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
|
||||||
|
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.unlockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||||
|
testScope.runTest {
|
||||||
|
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||||
|
mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
|
||||||
|
mAuthenticationInteractor.lockDevice()
|
||||||
|
runCurrent()
|
||||||
|
|
||||||
|
underTest.onContentClicked()
|
||||||
|
|
||||||
|
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTAINER_NAME = "container1"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user