Merge "[flexiglass] Hooks up real app state and logic in keyguard (3)" into udc-qpr-dev
This commit is contained in:
@@ -79,17 +79,25 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto
|
||||
import com.android.systemui.log.SessionTracker;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.scene.domain.interactor.SceneInteractor;
|
||||
import com.android.systemui.scene.shared.model.SceneContainerNames;
|
||||
import com.android.systemui.scene.shared.model.SceneKey;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.user.domain.interactor.UserInteractor;
|
||||
import com.android.systemui.util.ViewController;
|
||||
import com.android.systemui.util.kotlin.JavaAdapter;
|
||||
import com.android.systemui.util.settings.GlobalSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import kotlinx.coroutines.Job;
|
||||
|
||||
/** Controller for {@link KeyguardSecurityContainer} */
|
||||
@KeyguardBouncerScope
|
||||
@@ -378,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
|
||||
showPrimarySecurityScreen(false);
|
||||
}
|
||||
};
|
||||
private final UserInteractor mUserInteractor;
|
||||
private final Provider<SceneInteractor> mSceneInteractor;
|
||||
private final Provider<JavaAdapter> mJavaAdapter;
|
||||
@Nullable private Job mSceneTransitionCollectionJob;
|
||||
|
||||
@Inject
|
||||
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
|
||||
@@ -402,7 +414,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
|
||||
ViewMediatorCallback viewMediatorCallback,
|
||||
AudioManager audioManager,
|
||||
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
|
||||
BouncerMessageInteractor bouncerMessageInteractor
|
||||
BouncerMessageInteractor bouncerMessageInteractor,
|
||||
Provider<JavaAdapter> javaAdapter,
|
||||
UserInteractor userInteractor,
|
||||
Provider<SceneInteractor> sceneInteractor
|
||||
) {
|
||||
super(view);
|
||||
mLockPatternUtils = lockPatternUtils;
|
||||
@@ -429,6 +444,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
|
||||
mAudioManager = audioManager;
|
||||
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
|
||||
mBouncerMessageInteractor = bouncerMessageInteractor;
|
||||
mUserInteractor = userInteractor;
|
||||
mSceneInteractor = sceneInteractor;
|
||||
mJavaAdapter = javaAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -451,6 +469,24 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
|
||||
mView.setOnKeyListener(mOnKeyListener);
|
||||
|
||||
showPrimarySecurityScreen(false);
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
|
||||
// When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
|
||||
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
|
||||
mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
|
||||
sceneTransitionModel -> {
|
||||
if (sceneTransitionModel != null
|
||||
&& sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
|
||||
&& sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
|
||||
final int selectedUserId = mUserInteractor.getSelectedUserId();
|
||||
showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */ true,
|
||||
selectedUserId,
|
||||
/* bypassSecondaryLockScreen= */ true,
|
||||
mSecurityModel.getSecurityMode(selectedUserId));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -459,6 +495,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
|
||||
mConfigurationController.removeCallback(mConfigurationListener);
|
||||
mView.removeMotionEventListener(mGlobalTouchListener);
|
||||
mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
|
||||
|
||||
if (mSceneTransitionCollectionJob != null) {
|
||||
mSceneTransitionCollectionJob.cancel(null);
|
||||
mSceneTransitionCollectionJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
|
||||
@@ -14,25 +14,39 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.authentication.data.repository
|
||||
|
||||
import com.android.internal.widget.LockPatternChecker
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import java.util.function.Function
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -57,11 +71,17 @@ interface AuthenticationRepository {
|
||||
*/
|
||||
val isBypassEnabled: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Number of consecutively failed authentication attempts. This resets to `0` when
|
||||
* authentication succeeds.
|
||||
*/
|
||||
val failedAuthenticationAttempts: StateFlow<Int>
|
||||
/** Whether the auto confirm feature is enabled for the currently-selected user. */
|
||||
val isAutoConfirmEnabled: StateFlow<Boolean>
|
||||
|
||||
/** The length of the PIN for which we should show a hint. */
|
||||
val hintedPinLength: Int
|
||||
|
||||
/** Whether the pattern should be visible for the currently-selected user. */
|
||||
val isPatternVisible: StateFlow<Boolean>
|
||||
|
||||
/** The current throttling state, as cached via [setThrottling]. */
|
||||
val throttling: StateFlow<AuthenticationThrottlingModel>
|
||||
|
||||
/**
|
||||
* Returns the currently-configured authentication method. This determines how the
|
||||
@@ -69,11 +89,48 @@ interface AuthenticationRepository {
|
||||
*/
|
||||
suspend fun getAuthenticationMethod(): AuthenticationMethodModel
|
||||
|
||||
/** Returns the length of the PIN or `0` if the current auth method is not PIN. */
|
||||
suspend fun getPinLength(): Int
|
||||
|
||||
/**
|
||||
* Returns whether the lockscreen is enabled.
|
||||
*
|
||||
* When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
|
||||
* is considered not secure (for example, "swipe" is considered to be "none").
|
||||
*/
|
||||
suspend fun isLockscreenEnabled(): Boolean
|
||||
|
||||
/** See [isBypassEnabled]. */
|
||||
fun setBypassEnabled(isBypassEnabled: Boolean)
|
||||
|
||||
/** See [failedAuthenticationAttempts]. */
|
||||
fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
|
||||
/** Reports an authentication attempt. */
|
||||
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
|
||||
|
||||
/** Returns the current number of failed authentication attempts. */
|
||||
suspend fun getFailedAuthenticationAttemptCount(): Int
|
||||
|
||||
/**
|
||||
* Returns the timestamp for when the current throttling will end, allowing the user to attempt
|
||||
* authentication again.
|
||||
*
|
||||
* Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
|
||||
*/
|
||||
suspend fun getThrottlingEndTimestamp(): Long
|
||||
|
||||
/** Sets the cached throttling state, updating the [throttling] flow. */
|
||||
fun setThrottling(throttlingModel: AuthenticationThrottlingModel)
|
||||
|
||||
/**
|
||||
* Sets the throttling timeout duration (time during which the user should not be allowed to
|
||||
* attempt authentication).
|
||||
*/
|
||||
suspend fun setThrottleDuration(durationMs: Int)
|
||||
|
||||
/**
|
||||
* Checks the given [LockscreenCredential] to see if it's correct, returning an
|
||||
* [AuthenticationResultModel] representing what happened.
|
||||
*/
|
||||
suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
|
||||
}
|
||||
|
||||
class AuthenticationRepositoryImpl
|
||||
@@ -83,8 +140,8 @@ constructor(
|
||||
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
|
||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val userRepository: UserRepository,
|
||||
private val lockPatternUtils: LockPatternUtils,
|
||||
keyguardRepository: KeyguardRepository,
|
||||
private val lockPatternUtils: LockPatternUtils,
|
||||
) : AuthenticationRepository {
|
||||
|
||||
override val isUnlocked: StateFlow<Boolean> =
|
||||
@@ -94,54 +151,140 @@ constructor(
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
override suspend fun isLockscreenEnabled(): Boolean {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
!lockPatternUtils.isLockPatternEnabled(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
private val _isBypassEnabled = MutableStateFlow(false)
|
||||
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
|
||||
|
||||
private val _failedAuthenticationAttempts = MutableStateFlow(0)
|
||||
override val failedAuthenticationAttempts: StateFlow<Int> =
|
||||
_failedAuthenticationAttempts.asStateFlow()
|
||||
override val isAutoConfirmEnabled: StateFlow<Boolean> =
|
||||
userRepository.selectedUserInfo
|
||||
.map { it.id }
|
||||
.flatMapLatest { userId ->
|
||||
flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
|
||||
.flowOn(backgroundDispatcher)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = true,
|
||||
)
|
||||
|
||||
override val hintedPinLength: Int = LockPatternUtils.MIN_AUTO_PIN_REQUIREMENT_LENGTH
|
||||
|
||||
override val isPatternVisible: StateFlow<Boolean> =
|
||||
userRepository.selectedUserInfo
|
||||
.map { it.id }
|
||||
.flatMapLatest { userId ->
|
||||
flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
|
||||
.flowOn(backgroundDispatcher)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = true,
|
||||
)
|
||||
|
||||
private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
|
||||
override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
|
||||
|
||||
private val UserRepository.selectedUserId: Int
|
||||
get() = getSelectedUserInfo().id
|
||||
|
||||
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.getSelectedUserInfo().id
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
when (getSecurityMode.apply(selectedUserId)) {
|
||||
KeyguardSecurityModel.SecurityMode.PIN,
|
||||
KeyguardSecurityModel.SecurityMode.SimPin ->
|
||||
AuthenticationMethodModel.Pin(
|
||||
code = listOf(1, 2, 3, 4), // TODO(b/280883900): remove this
|
||||
autoConfirm = lockPatternUtils.isAutoPinConfirmEnabled(selectedUserId),
|
||||
)
|
||||
KeyguardSecurityModel.SecurityMode.Password,
|
||||
KeyguardSecurityModel.SecurityMode.SimPuk ->
|
||||
AuthenticationMethodModel.Password(
|
||||
password = "password", // TODO(b/280883900): remove this
|
||||
)
|
||||
KeyguardSecurityModel.SecurityMode.Pattern ->
|
||||
AuthenticationMethodModel.Pattern(
|
||||
coordinates =
|
||||
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),
|
||||
), // TODO(b/280883900): remove this
|
||||
)
|
||||
KeyguardSecurityModel.SecurityMode.SimPin,
|
||||
KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
|
||||
KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
|
||||
KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
|
||||
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
|
||||
KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
|
||||
null -> error("Invalid security is null!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPinLength(): Int {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
lockPatternUtils.getPinLength(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setBypassEnabled(isBypassEnabled: Boolean) {
|
||||
_isBypassEnabled.value = isBypassEnabled
|
||||
}
|
||||
|
||||
override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
|
||||
_failedAuthenticationAttempts.value = failedAuthenticationAttempts
|
||||
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
withContext(backgroundDispatcher) {
|
||||
if (isSuccessful) {
|
||||
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
|
||||
} else {
|
||||
lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getFailedAuthenticationAttemptCount(): Int {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getThrottlingEndTimestamp(): Long {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val selectedUserId = userRepository.selectedUserId
|
||||
lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
|
||||
_throttling.value = throttlingModel
|
||||
}
|
||||
|
||||
override suspend fun setThrottleDuration(durationMs: Int) {
|
||||
withContext(backgroundDispatcher) {
|
||||
lockPatternUtils.setLockoutAttemptDeadline(
|
||||
userRepository.selectedUserId,
|
||||
durationMs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkCredential(
|
||||
credential: LockscreenCredential
|
||||
): AuthenticationResultModel {
|
||||
return suspendCoroutine { continuation ->
|
||||
LockPatternChecker.checkCredential(
|
||||
lockPatternUtils,
|
||||
credential,
|
||||
userRepository.selectedUserId,
|
||||
object : LockPatternChecker.OnCheckCallback {
|
||||
override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
|
||||
continuation.resume(
|
||||
AuthenticationResultModel(
|
||||
isSuccessful = matched,
|
||||
throttleDurationMs = throttleTimeoutMs,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCancelled() {
|
||||
continuation.resume(AuthenticationResultModel(isSuccessful = false))
|
||||
}
|
||||
|
||||
override fun onEarlyMatched() = Unit
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,25 +16,43 @@
|
||||
|
||||
package com.android.systemui.authentication.domain.interactor
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import com.android.internal.widget.LockPatternView
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Hosts application business logic related to authentication. */
|
||||
@SysUISingleton
|
||||
class AuthenticationInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Application applicationScope: CoroutineScope,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val repository: AuthenticationRepository,
|
||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val userRepository: UserRepository,
|
||||
private val clock: SystemClock,
|
||||
) {
|
||||
/**
|
||||
* Whether the device is unlocked.
|
||||
@@ -67,18 +85,66 @@ constructor(
|
||||
*/
|
||||
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
|
||||
|
||||
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
|
||||
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
|
||||
|
||||
/**
|
||||
* Number of consecutively failed authentication attempts. This resets to `0` when
|
||||
* authentication succeeds.
|
||||
* Whether currently throttled and the user has to wait before being able to try another
|
||||
* authentication attempt.
|
||||
*/
|
||||
val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
|
||||
val isThrottled: StateFlow<Boolean> =
|
||||
throttling
|
||||
.map { it.remainingMs > 0 }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = throttling.value.remainingMs > 0,
|
||||
)
|
||||
|
||||
/** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
|
||||
val hintedPinLength: StateFlow<Int?> =
|
||||
flow { emit(repository.getPinLength()) }
|
||||
.map { currentPinLength ->
|
||||
// Hinting is enabled for 6-digit codes only
|
||||
currentPinLength.takeIf { repository.hintedPinLength == it }
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
/** Whether the auto confirm feature is enabled for the currently-selected user. */
|
||||
val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
|
||||
|
||||
/** Whether the pattern should be visible for the currently-selected user. */
|
||||
val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
|
||||
|
||||
private var throttlingCountdownJob: Job? = null
|
||||
|
||||
init {
|
||||
applicationScope.launch {
|
||||
userRepository.selectedUserInfo
|
||||
.map { it.id }
|
||||
.distinctUntilChanged()
|
||||
.collect { onSelectedUserChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently-configured authentication method. This determines how the
|
||||
* authentication challenge is completed in order to unlock an otherwise locked device.
|
||||
*/
|
||||
suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
return repository.getAuthenticationMethod()
|
||||
val authMethod = repository.getAuthenticationMethod()
|
||||
return if (
|
||||
authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
|
||||
) {
|
||||
// We treat "None" as "Swipe" when the lockscreen is enabled.
|
||||
AuthenticationMethodModel.Swipe
|
||||
} else {
|
||||
authMethod
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,39 +170,52 @@ constructor(
|
||||
* authentication failed, `null` if the check was not performed.
|
||||
*/
|
||||
suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
|
||||
val authMethod = getAuthenticationMethod()
|
||||
if (tryAutoConfirm) {
|
||||
if ((authMethod as? AuthenticationMethodModel.Pin)?.autoConfirm != true) {
|
||||
// Do not attempt to authenticate unless the PIN lock is set to auto-confirm.
|
||||
return null
|
||||
}
|
||||
|
||||
if (input.size < authMethod.code.size) {
|
||||
// Do not attempt to authenticate if the PIN has not yet the required amount of
|
||||
// digits. This intentionally only skip for shorter PINs; if the PIN is longer, the
|
||||
// layer above might have throttled this check, and the PIN should be rejected via
|
||||
// the auth code below.
|
||||
return null
|
||||
}
|
||||
if (input.isEmpty()) {
|
||||
throw IllegalArgumentException("Input was empty!")
|
||||
}
|
||||
|
||||
val isSuccessful =
|
||||
when (authMethod) {
|
||||
is AuthenticationMethodModel.Pin -> input.asCode() == authMethod.code
|
||||
is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
|
||||
is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
|
||||
else -> true
|
||||
val skipCheck =
|
||||
when {
|
||||
// We're being throttled, the UI layer should not have called this; skip the
|
||||
// attempt.
|
||||
isThrottled.value -> true
|
||||
// Auto-confirm attempt when the feature is not enabled; skip the attempt.
|
||||
tryAutoConfirm && !isAutoConfirmEnabled.value -> true
|
||||
// Auto-confirm should skip the attempt if the pin entered is too short.
|
||||
tryAutoConfirm && input.size < repository.getPinLength() -> true
|
||||
else -> false
|
||||
}
|
||||
if (skipCheck) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isSuccessful) {
|
||||
repository.setFailedAuthenticationAttempts(0)
|
||||
} else {
|
||||
repository.setFailedAuthenticationAttempts(
|
||||
repository.failedAuthenticationAttempts.value + 1
|
||||
// Attempt to authenticate:
|
||||
val authMethod = getAuthenticationMethod()
|
||||
val credential = authMethod.createCredential(input) ?: return null
|
||||
val authenticationResult = repository.checkCredential(credential)
|
||||
credential.zeroize()
|
||||
|
||||
if (authenticationResult.isSuccessful || !tryAutoConfirm) {
|
||||
repository.reportAuthenticationAttempt(
|
||||
isSuccessful = authenticationResult.isSuccessful,
|
||||
)
|
||||
}
|
||||
|
||||
return isSuccessful
|
||||
// Check if we need to throttle and, if so, kick off the throttle countdown:
|
||||
if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
|
||||
repository.setThrottleDuration(
|
||||
durationMs = authenticationResult.throttleDurationMs,
|
||||
)
|
||||
startThrottlingCountdown()
|
||||
}
|
||||
|
||||
if (authenticationResult.isSuccessful) {
|
||||
// Since authentication succeeded, we should refresh throttling to make sure that our
|
||||
// state is completely reflecting the upstream source of truth.
|
||||
refreshThrottling()
|
||||
}
|
||||
|
||||
return authenticationResult.isSuccessful
|
||||
}
|
||||
|
||||
/** See [isBypassEnabled]. */
|
||||
@@ -144,44 +223,67 @@ constructor(
|
||||
repository.setBypassEnabled(!repository.isBypassEnabled.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns a PIN code from the given list. It's assumed the given list elements are all
|
||||
* [Int] in the range [0-9].
|
||||
*/
|
||||
private fun List<Any>.asCode(): List<Int>? {
|
||||
if (isEmpty() || size > DevicePolicyManager.MAX_PASSWORD_LENGTH) {
|
||||
return null
|
||||
}
|
||||
|
||||
return map {
|
||||
require(it is Int && it in 0..9) {
|
||||
"Pin is required to be Int in range [0..9], but got $it"
|
||||
/** Starts refreshing the throttling state every second. */
|
||||
private suspend fun startThrottlingCountdown() {
|
||||
cancelCountdown()
|
||||
throttlingCountdownJob =
|
||||
applicationScope.launch {
|
||||
while (refreshThrottling() > 0) {
|
||||
delay(1.seconds.inWholeMilliseconds)
|
||||
}
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) } }
|
||||
}
|
||||
/** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
|
||||
private fun cancelCountdown() {
|
||||
throttlingCountdownJob?.cancel()
|
||||
throttlingCountdownJob = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) }
|
||||
}
|
||||
/** Notifies that the currently-selected user has changed. */
|
||||
private suspend fun onSelectedUserChanged() {
|
||||
cancelCountdown()
|
||||
if (refreshThrottling() > 0) {
|
||||
startThrottlingCountdown()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the throttling state, hydrating the repository with the latest state.
|
||||
*
|
||||
* @return The remaining time for the current throttling countdown, in milliseconds or `0` if
|
||||
* not being throttled.
|
||||
*/
|
||||
private suspend fun refreshThrottling(): Long {
|
||||
return withContext(backgroundDispatcher) {
|
||||
val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
|
||||
val deadline = async { repository.getThrottlingEndTimestamp() }
|
||||
val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
|
||||
repository.setThrottling(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount = failedAttemptCount.await(),
|
||||
remainingMs = remainingMs.toInt(),
|
||||
),
|
||||
)
|
||||
remainingMs
|
||||
}
|
||||
}
|
||||
|
||||
private fun AuthenticationMethodModel.createCredential(
|
||||
input: List<Any>
|
||||
): LockscreenCredential? {
|
||||
return when (this) {
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
LockscreenCredential.createPin(input.joinToString(""))
|
||||
is AuthenticationMethodModel.Password ->
|
||||
LockscreenCredential.createPassword(input.joinToString(""))
|
||||
is AuthenticationMethodModel.Pattern ->
|
||||
LockscreenCredential.createPattern(
|
||||
input
|
||||
.map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
|
||||
.map { LockPatternView.Cell.of(it.y, it.x) }
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.systemui.authentication.shared.model
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
|
||||
/** Enumerates all known authentication methods. */
|
||||
sealed class AuthenticationMethodModel(
|
||||
/**
|
||||
@@ -34,30 +32,11 @@ sealed class AuthenticationMethodModel(
|
||||
/** The most basic authentication method. The lock screen can be swiped away when displayed. */
|
||||
object Swipe : AuthenticationMethodModel(isSecure = false)
|
||||
|
||||
/**
|
||||
* Authentication method using a PIN.
|
||||
*
|
||||
* In practice, a pin is restricted to 16 decimal digits , see
|
||||
* [android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH]
|
||||
*/
|
||||
data class Pin(val code: List<Int>, val autoConfirm: Boolean) :
|
||||
AuthenticationMethodModel(isSecure = true) {
|
||||
object Pin : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
/** Convenience constructor for tests only. */
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
code: Long,
|
||||
autoConfirm: Boolean = false
|
||||
) : this(code.toString(10).map { it - '0' }, autoConfirm) {}
|
||||
}
|
||||
|
||||
data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
data class Pattern(
|
||||
val coordinates: List<PatternCoordinate>,
|
||||
val isPatternVisible: Boolean = true,
|
||||
) : AuthenticationMethodModel(isSecure = true) {
|
||||
object Password : AuthenticationMethodModel(isSecure = true)
|
||||
|
||||
object Pattern : AuthenticationMethodModel(isSecure = true) {
|
||||
data class PatternCoordinate(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.authentication.shared.model
|
||||
|
||||
/** Models the result of an authentication attempt. */
|
||||
data class AuthenticationResultModel(
|
||||
/** Whether authentication was successful. */
|
||||
val isSuccessful: Boolean = false,
|
||||
/** If [isSuccessful] is `false`, how long the user must wait before trying again. */
|
||||
val throttleDurationMs: Int = 0,
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.authentication.shared.model
|
||||
|
||||
/** Models a state for throttling the next authentication attempt. */
|
||||
data class AuthenticationThrottlingModel(
|
||||
|
||||
/** Number of failed authentication attempts so far. If not throttling this will be `0`. */
|
||||
val failedAttemptCount: Int = 0,
|
||||
|
||||
/**
|
||||
* Remaining amount of time, in milliseconds, before another authentication attempt can be done.
|
||||
* If not throttling this will be `0`.
|
||||
*
|
||||
* This number is changed throughout the timeout.
|
||||
*/
|
||||
val remainingMs: Int = 0,
|
||||
)
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.bouncer.data.repository
|
||||
|
||||
import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -30,15 +29,7 @@ class BouncerRepository @Inject constructor() {
|
||||
/** The user-facing message to show in the bouncer. */
|
||||
val message: StateFlow<String?> = _message.asStateFlow()
|
||||
|
||||
private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
|
||||
/** The current authentication throttling state. If `null`, there's no throttling. */
|
||||
val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
|
||||
|
||||
fun setMessage(message: String?) {
|
||||
_message.value = message
|
||||
}
|
||||
|
||||
fun setThrottling(throttling: AuthenticationThrottledModel?) {
|
||||
_throttling.value = throttling
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.bouncer.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.bouncer.data.repository.BouncerRepository
|
||||
import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
|
||||
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 kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -57,9 +60,10 @@ constructor(
|
||||
val message: StateFlow<String?> =
|
||||
combine(
|
||||
repository.message,
|
||||
repository.throttling,
|
||||
) { message, throttling ->
|
||||
messageOrThrottlingMessage(message, throttling)
|
||||
authenticationInteractor.isThrottled,
|
||||
authenticationInteractor.throttling,
|
||||
) { message, isThrottled, throttling ->
|
||||
messageOrThrottlingMessage(message, isThrottled, throttling)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
@@ -67,12 +71,28 @@ constructor(
|
||||
initialValue =
|
||||
messageOrThrottlingMessage(
|
||||
repository.message.value,
|
||||
repository.throttling.value,
|
||||
authenticationInteractor.isThrottled.value,
|
||||
authenticationInteractor.throttling.value,
|
||||
)
|
||||
)
|
||||
|
||||
/** The current authentication throttling state. If `null`, there's no throttling. */
|
||||
val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
|
||||
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
|
||||
val throttling: StateFlow<AuthenticationThrottlingModel> = authenticationInteractor.throttling
|
||||
|
||||
/**
|
||||
* Whether currently throttled and the user has to wait before being able to try another
|
||||
* authentication attempt.
|
||||
*/
|
||||
val isThrottled: StateFlow<Boolean> = authenticationInteractor.isThrottled
|
||||
|
||||
/** Whether the auto confirm feature is enabled for the currently-selected user. */
|
||||
val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
|
||||
|
||||
/** The length of the PIN for which we should show a hint. */
|
||||
val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
|
||||
|
||||
/** Whether the pattern should be visible for the currently-selected user. */
|
||||
val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
|
||||
|
||||
init {
|
||||
// UNLOCKING SHOWS Gone.
|
||||
@@ -98,6 +118,15 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the message if moved from throttling to no-longer throttling.
|
||||
applicationScope.launch {
|
||||
isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
|
||||
if (wasThrottled && !currentlyThrottled) {
|
||||
clearMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,41 +197,16 @@ constructor(
|
||||
input: List<Any>,
|
||||
tryAutoConfirm: Boolean = false,
|
||||
): Boolean? {
|
||||
if (repository.throttling.value != null) {
|
||||
return false
|
||||
}
|
||||
|
||||
val isAuthenticated =
|
||||
authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
|
||||
|
||||
val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
|
||||
when {
|
||||
isAuthenticated -> {
|
||||
repository.setThrottling(null)
|
||||
sceneInteractor.setCurrentScene(
|
||||
containerName = containerName,
|
||||
scene = SceneModel(SceneKey.Gone),
|
||||
)
|
||||
}
|
||||
failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
|
||||
applicationScope.launch {
|
||||
var remainingDurationSec = THROTTLE_DURATION_SEC
|
||||
while (remainingDurationSec > 0) {
|
||||
repository.setThrottling(
|
||||
AuthenticationThrottledModel(
|
||||
failedAttemptCount = failedAttempts,
|
||||
totalDurationSec = THROTTLE_DURATION_SEC,
|
||||
remainingDurationSec = remainingDurationSec,
|
||||
)
|
||||
)
|
||||
remainingDurationSec--
|
||||
delay(1000)
|
||||
}
|
||||
|
||||
repository.setThrottling(null)
|
||||
clearMessage()
|
||||
}
|
||||
else -> repository.setMessage(errorMessage(getAuthenticationMethod()))
|
||||
if (isAuthenticated) {
|
||||
sceneInteractor.setCurrentScene(
|
||||
containerName = containerName,
|
||||
scene = SceneModel(SceneKey.Gone),
|
||||
)
|
||||
} else {
|
||||
repository.setMessage(errorMessage(getAuthenticationMethod()))
|
||||
}
|
||||
|
||||
return isAuthenticated
|
||||
@@ -233,13 +237,14 @@ constructor(
|
||||
|
||||
private fun messageOrThrottlingMessage(
|
||||
message: String?,
|
||||
throttling: AuthenticationThrottledModel?,
|
||||
isThrottled: Boolean,
|
||||
throttlingModel: AuthenticationThrottlingModel,
|
||||
): String {
|
||||
return when {
|
||||
throttling != null ->
|
||||
isThrottled ->
|
||||
applicationContext.getString(
|
||||
com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
|
||||
throttling.remainingDurationSec,
|
||||
throttlingModel.remainingMs.milliseconds.inWholeSeconds,
|
||||
)
|
||||
message != null -> message
|
||||
else -> ""
|
||||
@@ -252,10 +257,4 @@ constructor(
|
||||
containerName: String,
|
||||
): BouncerInteractor
|
||||
}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting const val THROTTLE_DURATION_SEC = 30
|
||||
@VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
|
||||
@VisibleForTesting const val THROTTLE_EVERY = 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.bouncer.shared.model
|
||||
|
||||
/**
|
||||
* Models application state for when further authentication attempts are being throttled due to too
|
||||
* many consecutive failed authentication attempts.
|
||||
*/
|
||||
data class AuthenticationThrottledModel(
|
||||
/** Total number of failed attempts so far. */
|
||||
val failedAttemptCount: Int,
|
||||
/** Total amount of time the user has to wait before attempting again. */
|
||||
val totalDurationSec: Int,
|
||||
/** Remaining amount of time the user has to wait before attempting again. */
|
||||
val remainingDurationSec: Int,
|
||||
)
|
||||
@@ -14,19 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.util.kotlin.pairwise
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlin.math.ceil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -51,12 +54,12 @@ constructor(
|
||||
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
|
||||
|
||||
private val isInputEnabled: StateFlow<Boolean> =
|
||||
interactor.throttling
|
||||
.map { it == null }
|
||||
interactor.isThrottled
|
||||
.map { !it }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = interactor.throttling.value == null,
|
||||
initialValue = !interactor.isThrottled.value,
|
||||
)
|
||||
|
||||
private val pin: PinBouncerViewModel by lazy {
|
||||
@@ -115,9 +118,9 @@ constructor(
|
||||
val message: StateFlow<MessageViewModel> =
|
||||
combine(
|
||||
interactor.message,
|
||||
interactor.throttling,
|
||||
) { message, throttling ->
|
||||
toMessageViewModel(message, throttling)
|
||||
interactor.isThrottled,
|
||||
) { message, isThrottled ->
|
||||
toMessageViewModel(message, isThrottled)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
@@ -125,7 +128,7 @@ constructor(
|
||||
initialValue =
|
||||
toMessageViewModel(
|
||||
message = interactor.message.value,
|
||||
throttling = interactor.throttling.value,
|
||||
isThrottled = interactor.isThrottled.value,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -143,9 +146,9 @@ constructor(
|
||||
|
||||
init {
|
||||
applicationScope.launch {
|
||||
interactor.throttling
|
||||
.map { model ->
|
||||
model?.let {
|
||||
interactor.isThrottled
|
||||
.map { isThrottled ->
|
||||
if (isThrottled) {
|
||||
when (interactor.getAuthenticationMethod()) {
|
||||
is AuthenticationMethodModel.Pin ->
|
||||
R.string.kg_too_many_failed_pin_attempts_dialog_message
|
||||
@@ -157,10 +160,12 @@ constructor(
|
||||
}?.let { stringResourceId ->
|
||||
applicationContext.getString(
|
||||
stringResourceId,
|
||||
model.failedAttemptCount,
|
||||
model.totalDurationSec,
|
||||
interactor.throttling.value.failedAttemptCount,
|
||||
ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
@@ -184,11 +189,11 @@ constructor(
|
||||
|
||||
private fun toMessageViewModel(
|
||||
message: String?,
|
||||
throttling: AuthenticationThrottledModel?,
|
||||
isThrottled: Boolean,
|
||||
): MessageViewModel {
|
||||
return MessageViewModel(
|
||||
text = message ?: "",
|
||||
isUpdateAnimated = throttling == null,
|
||||
isUpdateAnimated = !isThrottled,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -70,19 +69,7 @@ class PatternBouncerViewModel(
|
||||
val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
|
||||
|
||||
/** Whether the pattern itself should be rendered visibly. */
|
||||
val isPatternVisible: StateFlow<Boolean> =
|
||||
flow {
|
||||
emit(null)
|
||||
emit(interactor.getAuthenticationMethod())
|
||||
}
|
||||
.map { authMethod ->
|
||||
(authMethod as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false,
|
||||
)
|
||||
val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
|
||||
|
||||
/** Notifies that the UI has been shown to the user. */
|
||||
fun onShown() {
|
||||
|
||||
@@ -18,13 +18,12 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.android.keyguard.PinShapeAdapter
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -45,26 +44,18 @@ class PinBouncerViewModel(
|
||||
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
|
||||
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
|
||||
|
||||
/** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
|
||||
val hintedPinLength: StateFlow<Int?> =
|
||||
flow { emit(interactor.getAuthenticationMethod()) }
|
||||
.map { authMethod ->
|
||||
// Hinting is enabled for 6-digit codes only
|
||||
autoConfirmPinLength(authMethod).takeIf { it == HINTING_PASSCODE_LENGTH }
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
)
|
||||
/** The length of the PIN for which we should show a hint. */
|
||||
val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
|
||||
|
||||
/** Appearance of the backspace button. */
|
||||
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
|
||||
mutablePinEntries
|
||||
.map { mutablePinEntries ->
|
||||
combine(
|
||||
mutablePinEntries,
|
||||
interactor.isAutoConfirmEnabled,
|
||||
) { mutablePinEntries, isAutoConfirmEnabled ->
|
||||
computeBackspaceButtonAppearance(
|
||||
interactor.getAuthenticationMethod(),
|
||||
mutablePinEntries
|
||||
enteredPin = mutablePinEntries,
|
||||
isAutoConfirmEnabled = isAutoConfirmEnabled,
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
@@ -75,11 +66,14 @@ class PinBouncerViewModel(
|
||||
|
||||
/** Appearance of the confirm button. */
|
||||
val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
|
||||
flow {
|
||||
emit(null)
|
||||
emit(interactor.getAuthenticationMethod())
|
||||
interactor.isAutoConfirmEnabled
|
||||
.map {
|
||||
if (it) {
|
||||
ActionButtonAppearance.Hidden
|
||||
} else {
|
||||
ActionButtonAppearance.Shown
|
||||
}
|
||||
}
|
||||
.map { authMethod -> computeConfirmButtonAppearance(authMethod) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
@@ -134,21 +128,10 @@ class PinBouncerViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAutoConfirmEnabled(authMethodModel: AuthenticationMethodModel?): Boolean {
|
||||
return (authMethodModel as? AuthenticationMethodModel.Pin)?.autoConfirm == true
|
||||
}
|
||||
|
||||
private fun autoConfirmPinLength(authMethodModel: AuthenticationMethodModel?): Int? {
|
||||
if (!isAutoConfirmEnabled(authMethodModel)) return null
|
||||
|
||||
return (authMethodModel as? AuthenticationMethodModel.Pin)?.code?.size
|
||||
}
|
||||
|
||||
private fun computeBackspaceButtonAppearance(
|
||||
authMethodModel: AuthenticationMethodModel,
|
||||
enteredPin: List<EnteredKey>
|
||||
enteredPin: List<EnteredKey>,
|
||||
isAutoConfirmEnabled: Boolean,
|
||||
): ActionButtonAppearance {
|
||||
val isAutoConfirmEnabled = isAutoConfirmEnabled(authMethodModel)
|
||||
val isEmpty = enteredPin.isEmpty()
|
||||
|
||||
return when {
|
||||
@@ -157,15 +140,6 @@ class PinBouncerViewModel(
|
||||
else -> ActionButtonAppearance.Shown
|
||||
}
|
||||
}
|
||||
private fun computeConfirmButtonAppearance(
|
||||
authMethodModel: AuthenticationMethodModel?
|
||||
): ActionButtonAppearance {
|
||||
return if (isAutoConfirmEnabled(authMethodModel)) {
|
||||
ActionButtonAppearance.Hidden
|
||||
} else {
|
||||
ActionButtonAppearance.Shown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Appearance of pin-pad action buttons. */
|
||||
@@ -178,9 +152,6 @@ enum class ActionButtonAppearance {
|
||||
Shown,
|
||||
}
|
||||
|
||||
/** Auto-confirm passcodes of exactly 6 digits show a length hint, see http://shortn/_IXlmSNbDh6 */
|
||||
private const val HINTING_PASSCODE_LENGTH = 6
|
||||
|
||||
private var nextSequenceNumber = 1
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.scene.data.repository
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.SceneTransitionModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -45,6 +46,9 @@ constructor(
|
||||
containerConfigByName
|
||||
.map { (containerName, _) -> containerName to MutableStateFlow(1f) }
|
||||
.toMap()
|
||||
private val sceneTransitionByContainerName:
|
||||
Map<String, MutableStateFlow<SceneTransitionModel?>> =
|
||||
containerConfigByName.keys.associateWith { MutableStateFlow(null) }
|
||||
|
||||
/**
|
||||
* Returns the keys to all scenes in the container with the given name.
|
||||
@@ -70,11 +74,43 @@ constructor(
|
||||
currentSceneByContainerName.setValue(containerName, scene)
|
||||
}
|
||||
|
||||
/** Sets the scene transition in the container with the given name. */
|
||||
fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
|
||||
check(allSceneKeys(containerName).contains(from)) {
|
||||
"""
|
||||
Cannot set current scene key to "$from". The container "$containerName" does
|
||||
not contain a scene with that key.
|
||||
"""
|
||||
.trimIndent()
|
||||
}
|
||||
check(allSceneKeys(containerName).contains(to)) {
|
||||
"""
|
||||
Cannot set current scene key to "$to". The container "$containerName" does
|
||||
not contain a scene with that key.
|
||||
"""
|
||||
.trimIndent()
|
||||
}
|
||||
|
||||
sceneTransitionByContainerName.setValue(
|
||||
containerName,
|
||||
SceneTransitionModel(from = from, to = to)
|
||||
)
|
||||
}
|
||||
|
||||
/** The current scene in the container with the given name. */
|
||||
fun currentScene(containerName: String): StateFlow<SceneModel> {
|
||||
return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
|
||||
* transition occurs. The flow begins with a `null` value at first, because the initial scene is
|
||||
* not something that we transition to from another scene.
|
||||
*/
|
||||
fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
|
||||
return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
|
||||
}
|
||||
|
||||
/** Sets whether the container with the given name is visible. */
|
||||
fun setVisible(containerName: String, isVisible: Boolean) {
|
||||
containerVisibilityByName.setValue(containerName, isVisible)
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.scene.data.repository.SceneContainerRepository
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.SceneTransitionModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -43,7 +44,9 @@ constructor(
|
||||
|
||||
/** Sets the scene in the container with the given name. */
|
||||
fun setCurrentScene(containerName: String, scene: SceneModel) {
|
||||
val currentSceneKey = repository.currentScene(containerName).value.key
|
||||
repository.setCurrentScene(containerName, scene)
|
||||
repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
|
||||
}
|
||||
|
||||
/** The current scene in the container with the given name. */
|
||||
@@ -70,4 +73,13 @@ constructor(
|
||||
fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
|
||||
return repository.sceneTransitionProgress(containerName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
|
||||
* transition occurs. The flow begins with a `null` value at first, because the initial scene is
|
||||
* not something that we transition to from another scene.
|
||||
*/
|
||||
fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
|
||||
return repository.sceneTransitions(containerName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.shared.model
|
||||
|
||||
/** Models a transition between two scenes. */
|
||||
data class SceneTransitionModel(
|
||||
/** The scene we transitioned away from. */
|
||||
val from: SceneKey,
|
||||
/** The scene we transitioned into. */
|
||||
val to: SceneKey,
|
||||
)
|
||||
@@ -532,6 +532,12 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the ID of the currently-selected user. */
|
||||
@UserIdInt
|
||||
fun getSelectedUserId(): Int {
|
||||
return repository.getSelectedUserInfo().id
|
||||
}
|
||||
|
||||
private fun showDialog(request: ShowDialogRequestModel) {
|
||||
_dialogShowRequests.value = request
|
||||
}
|
||||
@@ -774,17 +780,16 @@ constructor(
|
||||
}
|
||||
|
||||
// TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
|
||||
val userIcon = withContext(backgroundDispatcher) {
|
||||
manager.getUserIcon(userId)
|
||||
?.let { bitmap ->
|
||||
val userIcon =
|
||||
withContext(backgroundDispatcher) {
|
||||
manager.getUserIcon(userId)?.let { bitmap ->
|
||||
val iconSize =
|
||||
applicationContext
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size)
|
||||
applicationContext.resources.getDimensionPixelSize(
|
||||
R.dimen.bouncer_user_switcher_icon_size
|
||||
)
|
||||
Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (userIcon != null) {
|
||||
return BitmapDrawable(userIcon)
|
||||
|
||||
@@ -1,732 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.keyguard;
|
||||
|
||||
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
|
||||
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
|
||||
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.biometrics.BiometricOverlayConstants;
|
||||
import android.media.AudioManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableResources;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.biometrics.SideFpsController;
|
||||
import com.android.systemui.biometrics.SideFpsUiRequestSource;
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
|
||||
import com.android.systemui.classifier.FalsingA11yDelegate;
|
||||
import com.android.systemui.classifier.FalsingCollector;
|
||||
import com.android.systemui.flags.FakeFeatureFlags;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
|
||||
import com.android.systemui.log.SessionTracker;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.util.settings.GlobalSettings;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper()
|
||||
public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
|
||||
private static final int TARGET_USER_ID = 100;
|
||||
@Rule
|
||||
public MockitoRule mRule = MockitoJUnit.rule();
|
||||
@Mock
|
||||
private KeyguardSecurityContainer mView;
|
||||
@Mock
|
||||
private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
|
||||
@Mock
|
||||
private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
|
||||
@Mock
|
||||
private LockPatternUtils mLockPatternUtils;
|
||||
@Mock
|
||||
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
@Mock
|
||||
private KeyguardSecurityModel mKeyguardSecurityModel;
|
||||
@Mock
|
||||
private MetricsLogger mMetricsLogger;
|
||||
@Mock
|
||||
private UiEventLogger mUiEventLogger;
|
||||
@Mock
|
||||
private KeyguardStateController mKeyguardStateController;
|
||||
@Mock
|
||||
private KeyguardInputViewController mInputViewController;
|
||||
@Mock
|
||||
private WindowInsetsController mWindowInsetsController;
|
||||
@Mock
|
||||
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
|
||||
@Mock
|
||||
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
|
||||
@Mock
|
||||
private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
|
||||
@Mock
|
||||
private KeyguardMessageAreaController mKeyguardMessageAreaController;
|
||||
@Mock
|
||||
private BouncerKeyguardMessageArea mKeyguardMessageArea;
|
||||
@Mock
|
||||
private ConfigurationController mConfigurationController;
|
||||
@Mock
|
||||
private EmergencyButtonController mEmergencyButtonController;
|
||||
@Mock
|
||||
private FalsingCollector mFalsingCollector;
|
||||
@Mock
|
||||
private FalsingManager mFalsingManager;
|
||||
@Mock
|
||||
private GlobalSettings mGlobalSettings;
|
||||
@Mock
|
||||
private FeatureFlags mFeatureFlags;
|
||||
@Mock
|
||||
private UserSwitcherController mUserSwitcherController;
|
||||
@Mock
|
||||
private SessionTracker mSessionTracker;
|
||||
@Mock
|
||||
private KeyguardViewController mKeyguardViewController;
|
||||
@Mock
|
||||
private SideFpsController mSideFpsController;
|
||||
@Mock
|
||||
private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
|
||||
@Mock
|
||||
private FalsingA11yDelegate mFalsingA11yDelegate;
|
||||
@Mock
|
||||
private TelephonyManager mTelephonyManager;
|
||||
@Mock
|
||||
private ViewMediatorCallback mViewMediatorCallback;
|
||||
@Mock
|
||||
private AudioManager mAudioManager;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
|
||||
@Captor
|
||||
private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
|
||||
mOnViewInflatedCallbackArgumentCaptor;
|
||||
|
||||
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
|
||||
private KeyguardPasswordViewController mKeyguardPasswordViewController;
|
||||
private KeyguardPasswordView mKeyguardPasswordView;
|
||||
private TestableResources mTestableResources;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mTestableResources = mContext.getOrCreateTestableResources();
|
||||
mTestableResources.getResources().getConfiguration().orientation =
|
||||
Configuration.ORIENTATION_UNDEFINED;
|
||||
|
||||
when(mView.getContext()).thenReturn(mContext);
|
||||
when(mView.getResources()).thenReturn(mTestableResources.getResources());
|
||||
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width= */ 0, /* height= */
|
||||
0);
|
||||
lp.gravity = 0;
|
||||
when(mView.getLayoutParams()).thenReturn(lp);
|
||||
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
|
||||
.thenReturn(mAdminSecondaryLockScreenController);
|
||||
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
|
||||
mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate(
|
||||
R.layout.keyguard_password_view, null));
|
||||
when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
|
||||
when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area))
|
||||
.thenReturn(mKeyguardMessageArea);
|
||||
when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
|
||||
.thenReturn(mKeyguardMessageAreaController);
|
||||
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
|
||||
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
|
||||
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
|
||||
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
|
||||
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
|
||||
|
||||
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
|
||||
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
|
||||
SecurityMode.Password, mLockPatternUtils, null,
|
||||
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
|
||||
null, mock(Resources.class), null, mKeyguardViewController,
|
||||
featureFlags);
|
||||
|
||||
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
|
||||
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
|
||||
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
|
||||
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
|
||||
mConfigurationController, mFalsingCollector, mFalsingManager,
|
||||
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
|
||||
mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
|
||||
mTelephonyManager, mViewMediatorCallback, mAudioManager,
|
||||
mock(KeyguardFaceAuthInteractor.class),
|
||||
mock(BouncerMessageInteractor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInitConfiguresViewMode() {
|
||||
mKeyguardSecurityContainerController.onInit();
|
||||
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showSecurityScreen_canInflateAllModes() {
|
||||
SecurityMode[] modes = SecurityMode.values();
|
||||
for (SecurityMode mode : modes) {
|
||||
when(mInputViewController.getSecurityMode()).thenReturn(mode);
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(mode);
|
||||
if (mode == SecurityMode.Invalid) {
|
||||
verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
|
||||
any(SecurityMode.class), any(KeyguardSecurityCallback.class), any(
|
||||
KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
|
||||
);
|
||||
} else {
|
||||
verify(mKeyguardSecurityViewFlipperController).getSecurityView(
|
||||
eq(mode), any(KeyguardSecurityCallback.class), any(
|
||||
KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onResourcesUpdate_callsThroughOnRotationChange() {
|
||||
clearInvocations(mView);
|
||||
|
||||
// Rotation is the same, shouldn't cause an update
|
||||
mKeyguardSecurityContainerController.updateResources();
|
||||
verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
|
||||
// Update rotation. Should trigger update
|
||||
mTestableResources.getResources().getConfiguration().orientation =
|
||||
Configuration.ORIENTATION_LANDSCAPE;
|
||||
|
||||
mKeyguardSecurityContainerController.updateResources();
|
||||
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
}
|
||||
|
||||
private void touchDown() {
|
||||
mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
|
||||
MotionEvent.obtain(
|
||||
/* downTime= */0,
|
||||
/* eventTime= */0,
|
||||
MotionEvent.ACTION_DOWN,
|
||||
/* x= */0,
|
||||
/* y= */0,
|
||||
/* metaState= */0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
|
||||
|
||||
when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
|
||||
touchDown();
|
||||
verify(mFalsingCollector, never()).avoidGesture();
|
||||
|
||||
when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
|
||||
touchDown();
|
||||
verify(mFalsingCollector).avoidGesture();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
|
||||
mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false);
|
||||
setupGetSecurityView(SecurityMode.Pattern);
|
||||
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
|
||||
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
|
||||
mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
|
||||
setupGetSecurityView(SecurityMode.Pattern);
|
||||
verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
|
||||
mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
|
||||
setupGetSecurityView(SecurityMode.Password);
|
||||
|
||||
verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
|
||||
eq(mUserSwitcherController),
|
||||
any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
|
||||
eq(mFalsingA11yDelegate));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addUserSwitcherCallback() {
|
||||
ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
|
||||
captor = ArgumentCaptor.forClass(
|
||||
KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
|
||||
setupGetSecurityView(SecurityMode.Password);
|
||||
|
||||
verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
|
||||
any(UserSwitcherController.class),
|
||||
captor.capture(),
|
||||
eq(mFalsingA11yDelegate));
|
||||
captor.getValue().showUnlockToContinueMessage();
|
||||
getViewControllerImmediately();
|
||||
verify(mKeyguardPasswordViewControllerMock).showMessage(
|
||||
/* message= */ getContext().getString(R.string.keyguard_unlock_to_continue),
|
||||
/* colorState= */ null,
|
||||
/* animated= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addUserSwitchCallback() {
|
||||
mKeyguardSecurityContainerController.onViewAttached();
|
||||
verify(mUserSwitcherController)
|
||||
.addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
|
||||
mKeyguardSecurityContainerController.onViewDetached();
|
||||
verify(mUserSwitcherController)
|
||||
.removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onBouncerVisibilityChanged_resetsScale() {
|
||||
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
|
||||
verify(mView).resetScale();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
|
||||
// GIVEN the current security method is SimPin
|
||||
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
|
||||
when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN);
|
||||
mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */true,
|
||||
SecurityMode.SimPin);
|
||||
|
||||
// THEN the next security method of PIN is set, and the keyguard is not marked as done
|
||||
|
||||
verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt());
|
||||
verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
|
||||
assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode())
|
||||
.isEqualTo(SecurityMode.PIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
|
||||
// GIVEN the current security method is SimPin
|
||||
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
|
||||
when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
|
||||
when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
|
||||
mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */true,
|
||||
SecurityMode.SimPin);
|
||||
|
||||
// THEN the next security method of None will dismiss keyguard.
|
||||
verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
|
||||
//GIVEN current security mode has been set to PIN
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
|
||||
|
||||
//WHEN a request comes from SimPin to dismiss the security screens
|
||||
boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */true,
|
||||
SecurityMode.SimPin);
|
||||
|
||||
//THEN no action has happened, which will not dismiss the security screens
|
||||
assertThat(keyguardDone).isEqualTo(false);
|
||||
verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showNextSecurityScreenOrFinish_SimPin_Swipe() {
|
||||
// GIVEN the current security method is SimPin
|
||||
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
|
||||
when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
|
||||
// WHEN security method is SWIPE
|
||||
when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
|
||||
mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */true,
|
||||
SecurityMode.SimPin);
|
||||
|
||||
// THEN the next security method of None will dismiss keyguard.
|
||||
verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
|
||||
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
|
||||
getRegisteredSwipeListener();
|
||||
when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
|
||||
setupGetSecurityView(SecurityMode.Password);
|
||||
|
||||
registeredSwipeListener.onSwipeUp();
|
||||
|
||||
verify(mKeyguardUpdateMonitor).requestFaceAuth(
|
||||
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
|
||||
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
|
||||
getRegisteredSwipeListener();
|
||||
when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
|
||||
|
||||
registeredSwipeListener.onSwipeUp();
|
||||
|
||||
verify(mKeyguardUpdateMonitor, never())
|
||||
.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
|
||||
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
|
||||
getRegisteredSwipeListener();
|
||||
when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
|
||||
.thenReturn(true);
|
||||
setupGetSecurityView(SecurityMode.Password);
|
||||
|
||||
clearInvocations(mKeyguardSecurityViewFlipperController);
|
||||
registeredSwipeListener.onSwipeUp();
|
||||
getViewControllerImmediately();
|
||||
|
||||
verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */
|
||||
null, /* colorState= */ null, /* animated= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
|
||||
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
|
||||
getRegisteredSwipeListener();
|
||||
when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
|
||||
.thenReturn(false);
|
||||
setupGetSecurityView(SecurityMode.Password);
|
||||
|
||||
registeredSwipeListener.onSwipeUp();
|
||||
|
||||
verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */
|
||||
null, /* colorState= */ null, /* animated= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDensityOrFontScaleChanged() {
|
||||
ArgumentCaptor<ConfigurationController.ConfigurationListener>
|
||||
configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
|
||||
ConfigurationController.ConfigurationListener.class);
|
||||
mKeyguardSecurityContainerController.onViewAttached();
|
||||
verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
|
||||
clearInvocations(mKeyguardSecurityViewFlipperController);
|
||||
|
||||
configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
|
||||
|
||||
verify(mKeyguardSecurityViewFlipperController).clearViews();
|
||||
verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(KeyguardSecurityCallback.class),
|
||||
mOnViewInflatedCallbackArgumentCaptor.capture());
|
||||
|
||||
mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
|
||||
|
||||
verify(mView).onDensityOrFontScaleChanged();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onThemeChanged() {
|
||||
ArgumentCaptor<ConfigurationController.ConfigurationListener>
|
||||
configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
|
||||
ConfigurationController.ConfigurationListener.class);
|
||||
mKeyguardSecurityContainerController.onViewAttached();
|
||||
verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
|
||||
clearInvocations(mKeyguardSecurityViewFlipperController);
|
||||
|
||||
configurationListenerArgumentCaptor.getValue().onThemeChanged();
|
||||
|
||||
verify(mKeyguardSecurityViewFlipperController).clearViews();
|
||||
verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(KeyguardSecurityCallback.class),
|
||||
mOnViewInflatedCallbackArgumentCaptor.capture());
|
||||
|
||||
mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
|
||||
|
||||
verify(mView).reset();
|
||||
verify(mKeyguardSecurityViewFlipperController).reset();
|
||||
verify(mView).reloadColors();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onUiModeChanged() {
|
||||
ArgumentCaptor<ConfigurationController.ConfigurationListener>
|
||||
configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
|
||||
ConfigurationController.ConfigurationListener.class);
|
||||
mKeyguardSecurityContainerController.onViewAttached();
|
||||
verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
|
||||
clearInvocations(mKeyguardSecurityViewFlipperController);
|
||||
|
||||
configurationListenerArgumentCaptor.getValue().onUiModeChanged();
|
||||
|
||||
verify(mKeyguardSecurityViewFlipperController).clearViews();
|
||||
verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(KeyguardSecurityCallback.class),
|
||||
mOnViewInflatedCallbackArgumentCaptor.capture());
|
||||
|
||||
mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
|
||||
|
||||
verify(mView).reloadColors();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasDismissActions() {
|
||||
assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions());
|
||||
mKeyguardSecurityContainerController.setOnDismissAction(mock(
|
||||
ActivityStarter.OnDismissAction.class),
|
||||
null /* cancelAction */);
|
||||
assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWillRunDismissFromKeyguardIsTrue() {
|
||||
ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
|
||||
when(action.willRunAnimationOnKeyguard()).thenReturn(true);
|
||||
mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
|
||||
|
||||
mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
|
||||
|
||||
assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWillRunDismissFromKeyguardIsFalse() {
|
||||
ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
|
||||
when(action.willRunAnimationOnKeyguard()).thenReturn(false);
|
||||
mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
|
||||
|
||||
mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
|
||||
|
||||
assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
|
||||
mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
|
||||
null /* cancelAction */);
|
||||
|
||||
mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
|
||||
|
||||
assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartingToHide() {
|
||||
mKeyguardSecurityContainerController.onStartingToHide();
|
||||
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
|
||||
any(KeyguardSecurityCallback.class),
|
||||
mOnViewInflatedCallbackArgumentCaptor.capture());
|
||||
|
||||
mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
|
||||
verify(mInputViewController).onStartingToHide();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGravityReappliedOnConfigurationChange() {
|
||||
// Set initial gravity
|
||||
mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
|
||||
Gravity.CENTER);
|
||||
mTestableResources.addOverride(
|
||||
R.bool.can_use_one_handed_bouncer, false);
|
||||
|
||||
// Kick off the initial pass...
|
||||
mKeyguardSecurityContainerController.onInit();
|
||||
verify(mView).setLayoutParams(any());
|
||||
clearInvocations(mView);
|
||||
|
||||
// Now simulate a config change
|
||||
mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
|
||||
Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
|
||||
|
||||
mKeyguardSecurityContainerController.updateResources();
|
||||
verify(mView).setLayoutParams(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGravityUsesOneHandGravityWhenApplicable() {
|
||||
mTestableResources.addOverride(
|
||||
R.integer.keyguard_host_view_gravity,
|
||||
Gravity.CENTER);
|
||||
mTestableResources.addOverride(
|
||||
R.integer.keyguard_host_view_one_handed_gravity,
|
||||
Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
|
||||
|
||||
// Start disabled.
|
||||
mTestableResources.addOverride(
|
||||
R.bool.can_use_one_handed_bouncer, false);
|
||||
|
||||
mKeyguardSecurityContainerController.onInit();
|
||||
verify(mView).setLayoutParams(argThat(
|
||||
(ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
|
||||
argument.gravity == Gravity.CENTER));
|
||||
clearInvocations(mView);
|
||||
|
||||
// And enable
|
||||
mTestableResources.addOverride(
|
||||
R.bool.can_use_one_handed_bouncer, true);
|
||||
|
||||
mKeyguardSecurityContainerController.updateResources();
|
||||
verify(mView).setLayoutParams(argThat(
|
||||
(ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
|
||||
argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateKeyguardPositionDelegatesToSecurityContainer() {
|
||||
mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
|
||||
verify(mView).updatePositionByTouchX(1.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReinflateViewFlipper() {
|
||||
KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
|
||||
controller -> {
|
||||
};
|
||||
mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback);
|
||||
verify(mKeyguardSecurityViewFlipperController).clearViews();
|
||||
verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
|
||||
any(SecurityMode.class),
|
||||
any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSideFpsControllerShow() {
|
||||
mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
|
||||
verify(mSideFpsController).show(
|
||||
SideFpsUiRequestSource.PRIMARY_BOUNCER,
|
||||
BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSideFpsControllerHide() {
|
||||
mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
|
||||
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExpansion_setsAlpha() {
|
||||
mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
|
||||
|
||||
verify(mView).setAlpha(1f);
|
||||
verify(mView).setTranslationY(0f);
|
||||
}
|
||||
|
||||
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
|
||||
mKeyguardSecurityContainerController.onViewAttached();
|
||||
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
|
||||
return mSwipeListenerArgumentCaptor.getValue();
|
||||
}
|
||||
|
||||
private void setupGetSecurityView(SecurityMode securityMode) {
|
||||
mKeyguardSecurityContainerController.showSecurityScreen(securityMode);
|
||||
getViewControllerImmediately();
|
||||
}
|
||||
|
||||
private void getViewControllerImmediately() {
|
||||
verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView(
|
||||
any(SecurityMode.class), any(),
|
||||
mOnViewInflatedCallbackArgumentCaptor.capture());
|
||||
mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(
|
||||
(KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,810 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.keyguard
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.hardware.biometrics.BiometricOverlayConstants
|
||||
import android.media.AudioManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.testing.TestableResources
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowInsetsController
|
||||
import android.widget.FrameLayout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.MetricsLogger
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.biometrics.SideFpsController
|
||||
import com.android.systemui.biometrics.SideFpsUiRequestSource
|
||||
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
|
||||
import com.android.systemui.classifier.FalsingA11yDelegate
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.log.SessionTracker
|
||||
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
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.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController
|
||||
import com.android.systemui.user.domain.interactor.UserInteractor
|
||||
import com.android.systemui.util.kotlin.JavaAdapter
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argThat
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.settings.GlobalSettings
|
||||
import com.google.common.truth.Truth
|
||||
import java.util.Optional
|
||||
import junit.framework.Assert
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
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.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatcher
|
||||
import org.mockito.ArgumentMatchers.anyBoolean
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.atLeastOnce
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper
|
||||
class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var view: KeyguardSecurityContainer
|
||||
@Mock
|
||||
private lateinit var adminSecondaryLockScreenControllerFactory:
|
||||
AdminSecondaryLockScreenController.Factory
|
||||
@Mock
|
||||
private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
|
||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
|
||||
@Mock private lateinit var metricsLogger: MetricsLogger
|
||||
@Mock private lateinit var uiEventLogger: UiEventLogger
|
||||
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||
@Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
|
||||
@Mock private lateinit var windowInsetsController: WindowInsetsController
|
||||
@Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
|
||||
@Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
|
||||
@Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
|
||||
@Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
|
||||
@Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
|
||||
@Mock private lateinit var configurationController: ConfigurationController
|
||||
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
|
||||
@Mock private lateinit var falsingCollector: FalsingCollector
|
||||
@Mock private lateinit var falsingManager: FalsingManager
|
||||
@Mock private lateinit var globalSettings: GlobalSettings
|
||||
@Mock private lateinit var userSwitcherController: UserSwitcherController
|
||||
@Mock private lateinit var sessionTracker: SessionTracker
|
||||
@Mock private lateinit var keyguardViewController: KeyguardViewController
|
||||
@Mock private lateinit var sideFpsController: SideFpsController
|
||||
@Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
|
||||
@Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
|
||||
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||
@Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
|
||||
@Mock private lateinit var audioManager: AudioManager
|
||||
@Mock private lateinit var userInteractor: UserInteractor
|
||||
|
||||
@Captor
|
||||
private lateinit var swipeListenerArgumentCaptor:
|
||||
ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
|
||||
@Captor
|
||||
private lateinit var onViewInflatedCallbackArgumentCaptor:
|
||||
ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
|
||||
|
||||
private lateinit var featureFlags: FakeFeatureFlags
|
||||
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
|
||||
private lateinit var keyguardPasswordView: KeyguardPasswordView
|
||||
private lateinit var testableResources: TestableResources
|
||||
private lateinit var sceneTestUtils: SceneTestUtils
|
||||
private lateinit var sceneInteractor: SceneInteractor
|
||||
|
||||
private lateinit var underTest: KeyguardSecurityContainerController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
testableResources = mContext.getOrCreateTestableResources()
|
||||
testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
|
||||
whenever(view.context).thenReturn(mContext)
|
||||
whenever(view.resources).thenReturn(testableResources.resources)
|
||||
|
||||
val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
|
||||
lp.gravity = 0
|
||||
whenever(view.layoutParams).thenReturn(lp)
|
||||
|
||||
whenever(adminSecondaryLockScreenControllerFactory.create(any()))
|
||||
.thenReturn(adminSecondaryLockScreenController)
|
||||
whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
|
||||
keyguardPasswordView =
|
||||
spy(
|
||||
LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
|
||||
as KeyguardPasswordView
|
||||
)
|
||||
whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
|
||||
whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
|
||||
.thenReturn(keyguardMessageArea)
|
||||
whenever(messageAreaControllerFactory.create(any()))
|
||||
.thenReturn(keyguardMessageAreaController)
|
||||
whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
|
||||
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
|
||||
whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
|
||||
|
||||
featureFlags = FakeFeatureFlags()
|
||||
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||
featureFlags.set(Flags.SCENE_CONTAINER, false)
|
||||
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
|
||||
|
||||
keyguardPasswordViewController =
|
||||
KeyguardPasswordViewController(
|
||||
keyguardPasswordView,
|
||||
keyguardUpdateMonitor,
|
||||
SecurityMode.Password,
|
||||
lockPatternUtils,
|
||||
null,
|
||||
messageAreaControllerFactory,
|
||||
null,
|
||||
null,
|
||||
emergencyButtonController,
|
||||
null,
|
||||
mock(),
|
||||
null,
|
||||
keyguardViewController,
|
||||
featureFlags
|
||||
)
|
||||
|
||||
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
|
||||
sceneTestUtils = SceneTestUtils(this)
|
||||
sceneInteractor = sceneTestUtils.sceneInteractor()
|
||||
|
||||
underTest =
|
||||
KeyguardSecurityContainerController(
|
||||
view,
|
||||
adminSecondaryLockScreenControllerFactory,
|
||||
lockPatternUtils,
|
||||
keyguardUpdateMonitor,
|
||||
keyguardSecurityModel,
|
||||
metricsLogger,
|
||||
uiEventLogger,
|
||||
keyguardStateController,
|
||||
viewFlipperController,
|
||||
configurationController,
|
||||
falsingCollector,
|
||||
falsingManager,
|
||||
userSwitcherController,
|
||||
featureFlags,
|
||||
globalSettings,
|
||||
sessionTracker,
|
||||
Optional.of(sideFpsController),
|
||||
falsingA11yDelegate,
|
||||
telephonyManager,
|
||||
viewMediatorCallback,
|
||||
audioManager,
|
||||
mock(),
|
||||
mock(),
|
||||
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
|
||||
userInteractor,
|
||||
) {
|
||||
sceneInteractor
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onInitConfiguresViewMode() {
|
||||
underTest.onInit()
|
||||
verify(view)
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_DEFAULT),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showSecurityScreen_canInflateAllModes() {
|
||||
val modes = SecurityMode.values()
|
||||
for (mode in modes) {
|
||||
whenever(inputViewController.securityMode).thenReturn(mode)
|
||||
underTest.showSecurityScreen(mode)
|
||||
if (mode == SecurityMode.Invalid) {
|
||||
verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
|
||||
} else {
|
||||
verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onResourcesUpdate_callsThroughOnRotationChange() {
|
||||
clearInvocations(view)
|
||||
|
||||
// Rotation is the same, shouldn't cause an update
|
||||
underTest.updateResources()
|
||||
verify(view, never())
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_DEFAULT),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
|
||||
// Update rotation. Should trigger update
|
||||
testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
|
||||
underTest.updateResources()
|
||||
verify(view)
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_DEFAULT),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
}
|
||||
|
||||
private fun touchDown() {
|
||||
underTest.mGlobalTouchListener.onTouchEvent(
|
||||
MotionEvent.obtain(
|
||||
/* downTime= */ 0,
|
||||
/* eventTime= */ 0,
|
||||
MotionEvent.ACTION_DOWN,
|
||||
/* x= */ 0f,
|
||||
/* y= */ 0f,
|
||||
/* metaState= */ 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
|
||||
whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
|
||||
touchDown()
|
||||
verify(falsingCollector, never()).avoidGesture()
|
||||
whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
|
||||
touchDown()
|
||||
verify(falsingCollector).avoidGesture()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
|
||||
setupGetSecurityView(SecurityMode.Pattern)
|
||||
underTest.showSecurityScreen(SecurityMode.Pattern)
|
||||
verify(view)
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_DEFAULT),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
|
||||
setupGetSecurityView(SecurityMode.Pattern)
|
||||
verify(view)
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
|
||||
setupGetSecurityView(SecurityMode.Password)
|
||||
verify(view)
|
||||
.initMode(
|
||||
eq(KeyguardSecurityContainer.MODE_DEFAULT),
|
||||
eq(globalSettings),
|
||||
eq(falsingManager),
|
||||
eq(userSwitcherController),
|
||||
any(),
|
||||
eq(falsingA11yDelegate)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addUserSwitcherCallback() {
|
||||
val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
|
||||
setupGetSecurityView(SecurityMode.Password)
|
||||
verify(view)
|
||||
.initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
|
||||
captor.value.showUnlockToContinueMessage()
|
||||
viewControllerImmediately
|
||||
verify(keyguardPasswordViewControllerMock)
|
||||
.showMessage(
|
||||
/* message= */ context.getString(R.string.keyguard_unlock_to_continue),
|
||||
/* colorState= */ null,
|
||||
/* animated= */ true
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addUserSwitchCallback() {
|
||||
underTest.onViewAttached()
|
||||
verify(userSwitcherController).addUserSwitchCallback(any())
|
||||
underTest.onViewDetached()
|
||||
verify(userSwitcherController).removeUserSwitchCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBouncerVisibilityChanged_resetsScale() {
|
||||
underTest.onBouncerVisibilityChanged(false)
|
||||
verify(view).resetScale()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
|
||||
// GIVEN the current security method is SimPin
|
||||
whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
|
||||
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
|
||||
.thenReturn(false)
|
||||
underTest.showSecurityScreen(SecurityMode.SimPin)
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
|
||||
underTest.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */ true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */ true,
|
||||
SecurityMode.SimPin
|
||||
)
|
||||
|
||||
// THEN the next security method of PIN is set, and the keyguard is not marked as done
|
||||
verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
|
||||
// GIVEN the current security method is SimPin
|
||||
whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
|
||||
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
|
||||
.thenReturn(false)
|
||||
underTest.showSecurityScreen(SecurityMode.SimPin)
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
|
||||
.thenReturn(SecurityMode.None)
|
||||
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
|
||||
underTest.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */ true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */ true,
|
||||
SecurityMode.SimPin
|
||||
)
|
||||
|
||||
// THEN the next security method of None will dismiss keyguard.
|
||||
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
|
||||
// GIVEN current security mode has been set to PIN
|
||||
underTest.showSecurityScreen(SecurityMode.PIN)
|
||||
|
||||
// WHEN a request comes from SimPin to dismiss the security screens
|
||||
val keyguardDone =
|
||||
underTest.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */ true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */ true,
|
||||
SecurityMode.SimPin
|
||||
)
|
||||
|
||||
// THEN no action has happened, which will not dismiss the security screens
|
||||
Truth.assertThat(keyguardDone).isEqualTo(false)
|
||||
verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
|
||||
// GIVEN the current security method is SimPin
|
||||
whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
|
||||
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
|
||||
.thenReturn(false)
|
||||
underTest.showSecurityScreen(SecurityMode.SimPin)
|
||||
|
||||
// WHEN a request is made from the SimPin screens to show the next security method
|
||||
whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
|
||||
.thenReturn(SecurityMode.None)
|
||||
// WHEN security method is SWIPE
|
||||
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
|
||||
underTest.showNextSecurityScreenOrFinish(
|
||||
/* authenticated= */ true,
|
||||
TARGET_USER_ID,
|
||||
/* bypassSecondaryLockScreen= */ true,
|
||||
SecurityMode.SimPin
|
||||
)
|
||||
|
||||
// THEN the next security method of None will dismiss keyguard.
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
|
||||
val registeredSwipeListener = registeredSwipeListener
|
||||
whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
|
||||
setupGetSecurityView(SecurityMode.Password)
|
||||
registeredSwipeListener.onSwipeUp()
|
||||
verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
|
||||
val registeredSwipeListener = registeredSwipeListener
|
||||
whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
|
||||
registeredSwipeListener.onSwipeUp()
|
||||
verify(keyguardUpdateMonitor, never())
|
||||
.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
|
||||
val registeredSwipeListener = registeredSwipeListener
|
||||
whenever(
|
||||
keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
|
||||
)
|
||||
.thenReturn(true)
|
||||
setupGetSecurityView(SecurityMode.Password)
|
||||
clearInvocations(viewFlipperController)
|
||||
registeredSwipeListener.onSwipeUp()
|
||||
viewControllerImmediately
|
||||
verify(keyguardPasswordViewControllerMock)
|
||||
.showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
|
||||
val registeredSwipeListener = registeredSwipeListener
|
||||
whenever(
|
||||
keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
|
||||
)
|
||||
.thenReturn(false)
|
||||
setupGetSecurityView(SecurityMode.Password)
|
||||
registeredSwipeListener.onSwipeUp()
|
||||
verify(keyguardPasswordViewControllerMock, never())
|
||||
.showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onDensityOrFontScaleChanged() {
|
||||
val configurationListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
|
||||
underTest.onViewAttached()
|
||||
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
|
||||
clearInvocations(viewFlipperController)
|
||||
configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
|
||||
verify(viewFlipperController).clearViews()
|
||||
verify(viewFlipperController)
|
||||
.asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(),
|
||||
onViewInflatedCallbackArgumentCaptor.capture()
|
||||
)
|
||||
onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
|
||||
verify(view).onDensityOrFontScaleChanged()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onThemeChanged() {
|
||||
val configurationListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
|
||||
underTest.onViewAttached()
|
||||
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
|
||||
clearInvocations(viewFlipperController)
|
||||
configurationListenerArgumentCaptor.value.onThemeChanged()
|
||||
verify(viewFlipperController).clearViews()
|
||||
verify(viewFlipperController)
|
||||
.asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(),
|
||||
onViewInflatedCallbackArgumentCaptor.capture()
|
||||
)
|
||||
onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
|
||||
verify(view).reset()
|
||||
verify(viewFlipperController).reset()
|
||||
verify(view).reloadColors()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUiModeChanged() {
|
||||
val configurationListenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
|
||||
underTest.onViewAttached()
|
||||
verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
|
||||
clearInvocations(viewFlipperController)
|
||||
configurationListenerArgumentCaptor.value.onUiModeChanged()
|
||||
verify(viewFlipperController).clearViews()
|
||||
verify(viewFlipperController)
|
||||
.asynchronouslyInflateView(
|
||||
eq(SecurityMode.PIN),
|
||||
any(),
|
||||
onViewInflatedCallbackArgumentCaptor.capture()
|
||||
)
|
||||
onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
|
||||
verify(view).reloadColors()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasDismissActions() {
|
||||
Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
|
||||
underTest.setOnDismissAction(mock(), null /* cancelAction */)
|
||||
Assert.assertTrue("Action should exist", underTest.hasDismissActions())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun willRunDismissFromKeyguardIsTrue() {
|
||||
val action: OnDismissAction = mock()
|
||||
whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
|
||||
underTest.setOnDismissAction(action, null /* cancelAction */)
|
||||
underTest.finish(false /* strongAuth */, 0 /* currentUser */)
|
||||
Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun willRunDismissFromKeyguardIsFalse() {
|
||||
val action: OnDismissAction = mock()
|
||||
whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
|
||||
underTest.setOnDismissAction(action, null /* cancelAction */)
|
||||
underTest.finish(false /* strongAuth */, 0 /* currentUser */)
|
||||
Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
|
||||
underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
|
||||
underTest.finish(false /* strongAuth */, 0 /* currentUser */)
|
||||
Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onStartingToHide() {
|
||||
underTest.onStartingToHide()
|
||||
verify(viewFlipperController)
|
||||
.getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
|
||||
onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
|
||||
verify(inputViewController).onStartingToHide()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gravityReappliedOnConfigurationChange() {
|
||||
// Set initial gravity
|
||||
testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
|
||||
|
||||
// Kick off the initial pass...
|
||||
underTest.onInit()
|
||||
verify(view).layoutParams = any()
|
||||
clearInvocations(view)
|
||||
|
||||
// Now simulate a config change
|
||||
testableResources.addOverride(
|
||||
R.integer.keyguard_host_view_gravity,
|
||||
Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
|
||||
)
|
||||
underTest.updateResources()
|
||||
verify(view).layoutParams = any()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun gravityUsesOneHandGravityWhenApplicable() {
|
||||
testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
|
||||
testableResources.addOverride(
|
||||
R.integer.keyguard_host_view_one_handed_gravity,
|
||||
Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
|
||||
)
|
||||
|
||||
// Start disabled.
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
|
||||
underTest.onInit()
|
||||
verify(view).layoutParams =
|
||||
argThat(
|
||||
ArgumentMatcher { argument: FrameLayout.LayoutParams ->
|
||||
argument.gravity == Gravity.CENTER
|
||||
}
|
||||
as ArgumentMatcher<FrameLayout.LayoutParams>
|
||||
)
|
||||
clearInvocations(view)
|
||||
|
||||
// And enable
|
||||
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
|
||||
underTest.updateResources()
|
||||
verify(view).layoutParams =
|
||||
argThat(
|
||||
ArgumentMatcher { argument: FrameLayout.LayoutParams ->
|
||||
argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
|
||||
}
|
||||
as ArgumentMatcher<FrameLayout.LayoutParams>
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateKeyguardPositionDelegatesToSecurityContainer() {
|
||||
underTest.updateKeyguardPosition(1.0f)
|
||||
verify(view).updatePositionByTouchX(1.0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reinflateViewFlipper() {
|
||||
val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
|
||||
underTest.reinflateViewFlipper(onViewInflatedCallback)
|
||||
verify(viewFlipperController).clearViews()
|
||||
verify(viewFlipperController)
|
||||
.asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sideFpsControllerShow() {
|
||||
underTest.updateSideFpsVisibility(/* isVisible= */ true)
|
||||
verify(sideFpsController)
|
||||
.show(
|
||||
SideFpsUiRequestSource.PRIMARY_BOUNCER,
|
||||
BiometricOverlayConstants.REASON_AUTH_KEYGUARD
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sideFpsControllerHide() {
|
||||
underTest.updateSideFpsVisibility(/* isVisible= */ false)
|
||||
verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setExpansion_setsAlpha() {
|
||||
underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
|
||||
verify(view).alpha = 1f
|
||||
verify(view).translationY = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
|
||||
sceneTestUtils.testScope.runTest {
|
||||
featureFlags.set(Flags.SCENE_CONTAINER, true)
|
||||
|
||||
// Upon init, we have never dismisses the keyguard.
|
||||
underTest.onInit()
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// Once the view is attached, we start listening but simply going to the bouncer scene
|
||||
// is
|
||||
// not enough to trigger a dismissal of the keyguard.
|
||||
underTest.onViewAttached()
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// While listening, going from the bouncer scene to the gone scene, does dismiss the
|
||||
// keyguard.
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Gone, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// While listening, moving back to the bouncer scene does not dismiss the keyguard
|
||||
// again.
|
||||
clearInvocations(viewMediatorCallback)
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// Detaching the view stops listening, so moving from the bouncer scene to the gone
|
||||
// scene
|
||||
// does not dismiss the keyguard while we're not listening.
|
||||
underTest.onViewDetached()
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Gone, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// While not listening, moving back to the bouncer does not dismiss the keyguard.
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
|
||||
|
||||
// Reattaching the view starts listening again so moving from the bouncer scene to the
|
||||
// gone
|
||||
// scene now does dismiss the keyguard again.
|
||||
underTest.onViewAttached()
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Gone, null)
|
||||
)
|
||||
runCurrent()
|
||||
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
|
||||
}
|
||||
|
||||
private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
|
||||
get() {
|
||||
underTest.onViewAttached()
|
||||
verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
|
||||
return swipeListenerArgumentCaptor.value
|
||||
}
|
||||
|
||||
private fun setupGetSecurityView(securityMode: SecurityMode) {
|
||||
underTest.showSecurityScreen(securityMode)
|
||||
viewControllerImmediately
|
||||
}
|
||||
|
||||
private val viewControllerImmediately: Unit
|
||||
get() {
|
||||
verify(viewFlipperController, atLeastOnce())
|
||||
.getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
|
||||
keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TARGET_USER_ID = 100
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,16 @@ import android.app.admin.DevicePolicyManager
|
||||
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.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
@@ -47,24 +52,56 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun getAuthenticationMethod() =
|
||||
testScope.runTest {
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Pin(1234))
|
||||
assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Password("password"))
|
||||
.isEqualTo(AuthenticationMethodModel.Password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isUnlocked_whenAuthMethodIsNone_isTrue() =
|
||||
fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.Swipe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAuthenticationMethod_none_whenLockscreenDisabled() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(false)
|
||||
|
||||
assertThat(underTest.getAuthenticationMethod())
|
||||
.isEqualTo(AuthenticationMethodModel.None)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(false)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
assertThat(isUnlocked).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
|
||||
utils.authenticationRepository.setLockscreenEnabled(true)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toggleBypassEnabled() =
|
||||
testScope.runTest {
|
||||
@@ -84,7 +121,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isTrue()
|
||||
@@ -106,7 +143,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.isAuthenticationRequired()).isFalse()
|
||||
@@ -125,49 +162,33 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun authenticate_withCorrectPin_returnsTrue() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(isThrottled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withIncorrectPin_returnsFalse() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withEmptyPin_returnsFalse() =
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun authenticate_withEmptyPin_throwsException() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate(listOf())).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
underTest.authenticate(listOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(9999999999999999)
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate(List(16) { 9 })).isTrue()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
val pin = List(16) { 9 }
|
||||
utils.authenticationRepository.overrideCredential(pin)
|
||||
assertThat(underTest.authenticate(pin)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,105 +200,47 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
// If the policy changes, there is work to do in SysUI.
|
||||
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
|
||||
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(99999999999999999)
|
||||
)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectPassword_returnsTrue() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("password".toList())).isTrue()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(isThrottled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withIncorrectPassword_returnsFalse() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("alohomora".toList())).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withCorrectPattern_returnsTrue() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.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,
|
||||
),
|
||||
)
|
||||
)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
|
||||
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(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_withIncorrectPattern_returnsFalse() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.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,
|
||||
),
|
||||
)
|
||||
)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
|
||||
assertThat(
|
||||
@@ -299,91 +262,159 @@ class AuthenticationInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
)
|
||||
.isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withAutoConfirmPinAndEmptyInput_returnsNull() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate(listOf(), tryAutoConfirm = true)).isNull()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3), tryAutoConfirm = true)).isNull()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(isThrottled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4, 5), tryAutoConfirm = true))
|
||||
.isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 4, 4), tryAutoConfirm = true)).isFalse()
|
||||
assertThat(failedAttemptCount).isEqualTo(1)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isTrue()
|
||||
assertThat(isUnlocked).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
|
||||
)
|
||||
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(false)
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4), tryAutoConfirm = true)).isNull()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
|
||||
testScope.runTest {
|
||||
val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
|
||||
assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
|
||||
assertThat(failedAttemptCount).isEqualTo(0)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun throttling() =
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(underTest.isUnlocked)
|
||||
val throttling by collectLastValue(underTest.throttling)
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
underTest.authenticate(listOf(1, 2, 3, 4))
|
||||
assertThat(isUnlocked).isTrue()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
|
||||
// Make many wrong attempts, but just shy of what's needed to get throttled:
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
|
||||
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
}
|
||||
|
||||
// Make one more wrong attempt, leading to throttling:
|
||||
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(isThrottled).isTrue()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
|
||||
)
|
||||
)
|
||||
|
||||
// Correct PIN, but throttled, so doesn't attempt it:
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(isThrottled).isTrue()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
|
||||
)
|
||||
)
|
||||
|
||||
// Move the clock forward to ALMOST skip the throttling, leaving one second to go:
|
||||
val throttleTimeoutSec =
|
||||
FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
|
||||
.toInt()
|
||||
repeat(throttleTimeoutSec - 1) { time ->
|
||||
advanceTimeBy(1000)
|
||||
assertThat(isThrottled).isTrue()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository
|
||||
.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
remainingMs =
|
||||
((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
|
||||
.toInt(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Move the clock forward one more second, to completely finish the throttling period:
|
||||
advanceTimeBy(1000)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
remainingMs = 0,
|
||||
)
|
||||
)
|
||||
|
||||
// Correct PIN and no longer throttled so unlocks successfully:
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
|
||||
assertThat(isUnlocked).isTrue()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,16 @@ 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.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.math.ceil
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
@@ -65,14 +69,13 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun pinAuthMethod() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||
|
||||
@@ -98,14 +101,14 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||
underTest.clearMessage()
|
||||
@@ -128,14 +131,13 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.clearMessage()
|
||||
|
||||
@@ -153,13 +155,14 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun passwordAuthMethod() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
|
||||
|
||||
@@ -185,13 +188,14 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun patternAuthMethod() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(emptyList())
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||
|
||||
@@ -204,7 +208,7 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
// Wrong input.
|
||||
assertThat(
|
||||
underTest.authenticate(
|
||||
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
|
||||
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
|
||||
)
|
||||
)
|
||||
.isFalse()
|
||||
@@ -215,21 +219,20 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
|
||||
|
||||
// Correct input.
|
||||
assertThat(underTest.authenticate(emptyList())).isTrue()
|
||||
assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||
}
|
||||
@@ -237,11 +240,12 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
underTest.showOrUnlockDevice("container1")
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
|
||||
}
|
||||
@@ -249,15 +253,16 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun showOrUnlockDevice_customMessageShown() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(underTest.message)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
val customMessage = "Hello there!"
|
||||
underTest.showOrUnlockDevice("container1", customMessage)
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
|
||||
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
assertThat(message).isEqualTo(customMessage)
|
||||
@@ -266,50 +271,78 @@ class BouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun throttling() =
|
||||
testScope.runTest {
|
||||
val isThrottled by collectLastValue(underTest.isThrottled)
|
||||
val throttling by collectLastValue(underTest.throttling)
|
||||
val message by collectLastValue(underTest.message)
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
runCurrent()
|
||||
underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
|
||||
runCurrent()
|
||||
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
|
||||
assertThat(throttling).isNull()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
|
||||
// Wrong PIN.
|
||||
assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
|
||||
if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
|
||||
if (
|
||||
times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
|
||||
) {
|
||||
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
|
||||
}
|
||||
}
|
||||
assertThat(throttling).isNotNull()
|
||||
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
|
||||
assertThat(isThrottled).isTrue()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
|
||||
)
|
||||
)
|
||||
assertTryAgainMessage(
|
||||
message,
|
||||
FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
|
||||
.toInt()
|
||||
)
|
||||
|
||||
// Correct PIN, but throttled, so doesn't change away from the bouncer scene:
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isNull()
|
||||
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
|
||||
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
|
||||
assertTryAgainMessage(
|
||||
message,
|
||||
FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
|
||||
.toInt()
|
||||
)
|
||||
|
||||
throttling?.totalDurationSec?.let { seconds ->
|
||||
throttling?.remainingMs?.let { remainingMs ->
|
||||
val seconds = ceil(remainingMs / 1000f).toInt()
|
||||
repeat(seconds) { time ->
|
||||
advanceTimeBy(1000)
|
||||
val remainingTime = seconds - time - 1
|
||||
if (remainingTime > 0) {
|
||||
assertTryAgainMessage(message, remainingTime)
|
||||
val remainingTimeSec = seconds - time - 1
|
||||
if (remainingTimeSec > 0) {
|
||||
assertTryAgainMessage(message, remainingTimeSec)
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(message).isEqualTo("")
|
||||
assertThat(throttling).isNull()
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling)
|
||||
.isEqualTo(
|
||||
AuthenticationThrottlingModel(
|
||||
failedAttemptCount =
|
||||
FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
|
||||
)
|
||||
)
|
||||
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
|
||||
|
||||
// Correct PIN and no longer throttled so changes to the Gone scene:
|
||||
assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
|
||||
assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
|
||||
assertThat(isThrottled).isFalse()
|
||||
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -55,10 +55,8 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun animateFailure() =
|
||||
testScope.runTest {
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
val animateFailure by collectLastValue(underTest.animateFailure)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(animateFailure).isFalse()
|
||||
|
||||
// Wrong PIN:
|
||||
|
||||
@@ -18,8 +18,8 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -110,18 +110,16 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val message by collectLastValue(underTest.message)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(message?.isUpdateAnimated).isTrue()
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
// Wrong PIN.
|
||||
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
|
||||
}
|
||||
assertThat(message?.isUpdateAnimated).isFalse()
|
||||
|
||||
throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
|
||||
throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
|
||||
assertThat(message?.isUpdateAnimated).isTrue()
|
||||
}
|
||||
|
||||
@@ -135,18 +133,16 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
)
|
||||
val throttling by collectLastValue(bouncerInteractor.throttling)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(isInputEnabled).isTrue()
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
// Wrong PIN.
|
||||
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
|
||||
}
|
||||
assertThat(isInputEnabled).isFalse()
|
||||
|
||||
throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
|
||||
throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
|
||||
assertThat(isInputEnabled).isTrue()
|
||||
}
|
||||
|
||||
@@ -154,11 +150,9 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
fun throttlingDialogMessage() =
|
||||
testScope.runTest {
|
||||
val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
|
||||
repeat(BouncerInteractor.THROTTLE_EVERY) {
|
||||
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
|
||||
// Wrong PIN.
|
||||
assertThat(throttlingDialogMessage).isNull()
|
||||
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
|
||||
@@ -173,11 +167,9 @@ class BouncerViewModelTest : SysuiTestCase() {
|
||||
return listOf(
|
||||
AuthenticationMethodModel.None,
|
||||
AuthenticationMethodModel.Swipe,
|
||||
AuthenticationMethodModel.Pin(1234),
|
||||
AuthenticationMethodModel.Password("password"),
|
||||
AuthenticationMethodModel.Pattern(
|
||||
listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
|
||||
),
|
||||
AuthenticationMethodModel.Pin,
|
||||
AuthenticationMethodModel.Password,
|
||||
AuthenticationMethodModel.Pattern,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +72,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onShown() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val password by collectLastValue(underTest.password)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
|
||||
underTest.onShown()
|
||||
@@ -92,14 +96,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onPasswordInputChanged() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val password by collectLastValue(underTest.password)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
runCurrent()
|
||||
@@ -114,12 +122,16 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateKeyPressed_whenCorrect() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPasswordInputChanged("password")
|
||||
@@ -132,14 +144,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateKeyPressed_whenWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val password by collectLastValue(underTest.password)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPasswordInputChanged("wrong")
|
||||
@@ -154,14 +170,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateKeyPressed_correctAfterWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val password by collectLastValue(underTest.password)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPasswordInputChanged("wrong")
|
||||
@@ -180,7 +200,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTAINER_NAME = "container1"
|
||||
private const val ENTER_YOUR_PASSWORD = "Enter your password"
|
||||
private const val WRONG_PASSWORD = "Wrong password"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
@@ -74,15 +75,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onShown() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val selectedDots by collectLastValue(underTest.selectedDots)
|
||||
val currentDot by collectLastValue(underTest.currentDot)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
|
||||
underTest.onShown()
|
||||
@@ -96,15 +101,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onDragStart() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val selectedDots by collectLastValue(underTest.selectedDots)
|
||||
val currentDot by collectLastValue(underTest.currentDot)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
runCurrent()
|
||||
@@ -120,14 +129,18 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onDragEnd_whenCorrect() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val selectedDots by collectLastValue(underTest.selectedDots)
|
||||
val currentDot by collectLastValue(underTest.currentDot)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onDragStart()
|
||||
@@ -167,15 +180,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onDragEnd_whenWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val selectedDots by collectLastValue(underTest.selectedDots)
|
||||
val currentDot by collectLastValue(underTest.currentDot)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onDragStart()
|
||||
@@ -199,15 +216,19 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onDragEnd_correctAfterWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val selectedDots by collectLastValue(underTest.selectedDots)
|
||||
val currentDot by collectLastValue(underTest.currentDot)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
|
||||
AuthenticationMethodModel.Pattern
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onDragStart()
|
||||
@@ -241,20 +262,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTAINER_NAME = "container1"
|
||||
private const val ENTER_YOUR_PATTERN = "Enter your pattern"
|
||||
private const val WRONG_PATTERN = "Wrong pattern"
|
||||
private val CORRECT_PATTERN =
|
||||
listOf(
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
|
||||
)
|
||||
private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
return bouncerInteractor
|
||||
}
|
||||
},
|
||||
containerName = CONTAINER_NAME,
|
||||
containerName = SceneTestUtils.CONTAINER_1,
|
||||
)
|
||||
private val underTest =
|
||||
PinBouncerViewModel(
|
||||
@@ -82,11 +82,15 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onShown() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
|
||||
underTest.onShown()
|
||||
@@ -99,14 +103,16 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onPinButtonClicked() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
runCurrent()
|
||||
@@ -122,14 +128,16 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onBackspaceButtonClicked() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
runCurrent()
|
||||
@@ -146,13 +154,15 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onPinEdit() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
|
||||
@@ -172,14 +182,16 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onBackspaceButtonLongPressed() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
runCurrent()
|
||||
@@ -198,12 +210,14 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateButtonClicked_whenCorrect() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
@@ -219,14 +233,16 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateButtonClicked_whenWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
@@ -245,14 +261,16 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAuthenticateButtonClicked_correctAfterWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
@@ -280,12 +298,15 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAutoConfirm_whenCorrect() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
@@ -299,14 +320,17 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun onAutoConfirm_whenWrong() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
|
||||
val currentScene by
|
||||
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
val message by collectLastValue(bouncerViewModel.message)
|
||||
val entries by collectLastValue(underTest.pinEntries)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
sceneInteractor.setCurrentScene(
|
||||
SceneTestUtils.CONTAINER_1,
|
||||
SceneModel(SceneKey.Bouncer)
|
||||
)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
|
||||
underTest.onShown()
|
||||
underTest.onPinButtonClicked(1)
|
||||
@@ -324,9 +348,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
|
||||
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
|
||||
}
|
||||
@@ -335,9 +357,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
|
||||
testScope.runTest {
|
||||
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
|
||||
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
|
||||
}
|
||||
@@ -346,9 +367,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
|
||||
testScope.runTest {
|
||||
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
|
||||
underTest.onPinButtonClicked(1)
|
||||
|
||||
@@ -360,9 +380,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
|
||||
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
|
||||
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
|
||||
}
|
||||
@@ -371,9 +389,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
|
||||
testScope.runTest {
|
||||
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
|
||||
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
|
||||
}
|
||||
@@ -382,9 +399,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withoutAutoConfirm_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234, autoConfirm = false)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(false)
|
||||
|
||||
assertThat(hintedPinLength).isNull()
|
||||
}
|
||||
@@ -393,9 +409,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinLessThanSixDigits_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(12345, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
|
||||
assertThat(hintedPinLength).isNull()
|
||||
}
|
||||
@@ -404,9 +419,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinExactlySixDigits_isSix() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(123456, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
utils.authenticationRepository.overrideCredential(listOf(1, 2, 3, 4, 5, 6))
|
||||
|
||||
assertThat(hintedPinLength).isEqualTo(6)
|
||||
}
|
||||
@@ -415,15 +430,13 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
fun hintedPinLength_withAutoConfirmPinMoreThanSixDigits_isNull() =
|
||||
testScope.runTest {
|
||||
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234567, autoConfirm = true)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setAutoConfirmEnabled(true)
|
||||
|
||||
assertThat(hintedPinLength).isNull()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTAINER_NAME = "container1"
|
||||
private const val ENTER_YOUR_PIN = "Enter your pin"
|
||||
private const val WRONG_PIN = "Wrong pin"
|
||||
}
|
||||
|
||||
@@ -94,9 +94,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
@@ -109,9 +107,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.dismissLockscreen()
|
||||
@@ -153,9 +149,7 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
|
||||
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
assertThat(isUnlocked).isFalse()
|
||||
|
||||
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
|
||||
|
||||
@@ -73,7 +73,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
@@ -86,7 +86,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Password("password")
|
||||
AuthenticationMethodModel.Password
|
||||
)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
|
||||
@@ -108,9 +108,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||
@@ -120,9 +118,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
|
||||
@@ -135,9 +131,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
|
||||
@@ -150,9 +144,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
|
||||
@@ -165,9 +157,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
|
||||
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
|
||||
|
||||
@@ -69,9 +69,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
|
||||
@@ -84,9 +82,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.SceneTransitionModel
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -40,7 +41,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun allSceneKeys() {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
assertThat(underTest.allSceneKeys("container1"))
|
||||
assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
SceneKey.QuickSettings,
|
||||
@@ -61,10 +62,10 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun currentScene() = runTest {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
val currentScene by collectLastValue(underTest.currentScene("container1"))
|
||||
val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
|
||||
underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
|
||||
}
|
||||
|
||||
@@ -85,26 +86,26 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
|
||||
val underTest =
|
||||
utils.fakeSceneContainerRepository(
|
||||
setOf(
|
||||
utils.fakeSceneContainerConfig("container1"),
|
||||
utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
|
||||
utils.fakeSceneContainerConfig(
|
||||
"container2",
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
|
||||
),
|
||||
)
|
||||
)
|
||||
underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
|
||||
underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isVisible() = runTest {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
val isVisible by collectLastValue(underTest.isVisible("container1"))
|
||||
val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
underTest.setVisible("container1", false)
|
||||
underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
|
||||
assertThat(isVisible).isFalse()
|
||||
|
||||
underTest.setVisible("container1", true)
|
||||
underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
|
||||
assertThat(isVisible).isTrue()
|
||||
}
|
||||
|
||||
@@ -124,13 +125,13 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
|
||||
fun sceneTransitionProgress() = runTest {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
val sceneTransitionProgress by
|
||||
collectLastValue(underTest.sceneTransitionProgress("container1"))
|
||||
collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(sceneTransitionProgress).isEqualTo(1f)
|
||||
|
||||
underTest.setSceneTransitionProgress("container1", 0.1f)
|
||||
underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
|
||||
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
|
||||
|
||||
underTest.setSceneTransitionProgress("container1", 0.9f)
|
||||
underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
|
||||
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
|
||||
}
|
||||
|
||||
@@ -139,4 +140,75 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
underTest.sceneTransitionProgress("nonExistingContainer")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setSceneTransition() = runTest {
|
||||
val underTest =
|
||||
utils.fakeSceneContainerRepository(
|
||||
setOf(
|
||||
utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
|
||||
utils.fakeSceneContainerConfig(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
|
||||
),
|
||||
)
|
||||
)
|
||||
val sceneTransition by
|
||||
collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
|
||||
assertThat(sceneTransition).isNull()
|
||||
|
||||
underTest.setSceneTransition(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
SceneKey.Lockscreen,
|
||||
SceneKey.QuickSettings
|
||||
)
|
||||
assertThat(sceneTransition)
|
||||
.isEqualTo(
|
||||
SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun setSceneTransition_noSuchContainer_throws() {
|
||||
val underTest = utils.fakeSceneContainerRepository()
|
||||
underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun setSceneTransition_noFromSceneInContainer_throws() {
|
||||
val underTest =
|
||||
utils.fakeSceneContainerRepository(
|
||||
setOf(
|
||||
utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
|
||||
utils.fakeSceneContainerConfig(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
|
||||
),
|
||||
)
|
||||
)
|
||||
underTest.setSceneTransition(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
SceneKey.Shade,
|
||||
SceneKey.Lockscreen
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun setSceneTransition_noToSceneInContainer_throws() {
|
||||
val underTest =
|
||||
utils.fakeSceneContainerRepository(
|
||||
setOf(
|
||||
utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
|
||||
utils.fakeSceneContainerConfig(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
|
||||
),
|
||||
)
|
||||
)
|
||||
underTest.setSceneTransition(
|
||||
SceneTestUtils.CONTAINER_2,
|
||||
SceneKey.Shade,
|
||||
SceneKey.Lockscreen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.SceneTransitionModel
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -40,36 +41,63 @@ class SceneInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun allSceneKeys() {
|
||||
assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
|
||||
assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
|
||||
.isEqualTo(utils.fakeSceneKeys())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sceneTransitions() = runTest {
|
||||
val currentScene by collectLastValue(underTest.currentScene("container1"))
|
||||
fun currentScene() = runTest {
|
||||
val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
|
||||
|
||||
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
|
||||
underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
|
||||
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sceneTransitionProgress() = runTest {
|
||||
val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
|
||||
val progress by
|
||||
collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(progress).isEqualTo(1f)
|
||||
|
||||
underTest.setSceneTransitionProgress("container1", 0.55f)
|
||||
underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
|
||||
assertThat(progress).isEqualTo(0.55f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isVisible() = runTest {
|
||||
val isVisible by collectLastValue(underTest.isVisible("container1"))
|
||||
val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
underTest.setVisible("container1", false)
|
||||
underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
|
||||
assertThat(isVisible).isFalse()
|
||||
|
||||
underTest.setVisible("container1", true)
|
||||
underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
|
||||
assertThat(isVisible).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sceneTransitions() = runTest {
|
||||
val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
|
||||
assertThat(transitions).isNull()
|
||||
|
||||
val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
|
||||
underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
|
||||
assertThat(transitions)
|
||||
.isEqualTo(
|
||||
SceneTransitionModel(
|
||||
from = initialSceneKey,
|
||||
to = SceneKey.Shade,
|
||||
)
|
||||
)
|
||||
|
||||
underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
|
||||
assertThat(transitions)
|
||||
.isEqualTo(
|
||||
SceneTransitionModel(
|
||||
from = SceneKey.Shade,
|
||||
to = SceneKey.QuickSettings,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
|
||||
private val underTest =
|
||||
SceneContainerViewModel(
|
||||
interactor = interactor,
|
||||
containerName = "container1",
|
||||
containerName = SceneTestUtils.CONTAINER_1,
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -48,10 +48,10 @@ class SceneContainerViewModelTest : SysuiTestCase() {
|
||||
val isVisible by collectLastValue(underTest.isVisible)
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
interactor.setVisible("container1", false)
|
||||
interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
|
||||
assertThat(isVisible).isFalse()
|
||||
|
||||
interactor.setVisible("container1", true)
|
||||
interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
|
||||
assertThat(isVisible).isTrue()
|
||||
}
|
||||
|
||||
|
||||
@@ -70,9 +70,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_deviceLocked_lockScreen() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
@@ -82,9 +80,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun upTransitionSceneKey_deviceUnlocked_gone() =
|
||||
testScope.runTest {
|
||||
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
|
||||
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
|
||||
@@ -94,9 +90,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceUnlocked_switchesToGone() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
|
||||
@@ -109,9 +103,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
|
||||
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
|
||||
testScope.runTest {
|
||||
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
|
||||
utils.authenticationRepository.setAuthenticationMethod(
|
||||
AuthenticationMethodModel.Pin(1234)
|
||||
)
|
||||
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
|
||||
utils.authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
|
||||
|
||||
@@ -16,40 +16,154 @@
|
||||
|
||||
package com.android.systemui.authentication.data.repository
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.internal.widget.LockPatternView
|
||||
import com.android.internal.widget.LockscreenCredential
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class FakeAuthenticationRepository(
|
||||
private val delegate: AuthenticationRepository,
|
||||
private val onSecurityModeChanged: (SecurityMode) -> Unit,
|
||||
) : AuthenticationRepository by delegate {
|
||||
private val currentTime: () -> Long,
|
||||
) : AuthenticationRepository {
|
||||
|
||||
private val _isBypassEnabled = MutableStateFlow(false)
|
||||
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
|
||||
|
||||
private val _isAutoConfirmEnabled = MutableStateFlow(false)
|
||||
override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
|
||||
|
||||
private val _isUnlocked = MutableStateFlow(false)
|
||||
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
|
||||
|
||||
private var authenticationMethod: AuthenticationMethodModel = DEFAULT_AUTHENTICATION_METHOD
|
||||
override val hintedPinLength: Int = 6
|
||||
|
||||
private val _isPatternVisible = MutableStateFlow(true)
|
||||
override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
|
||||
|
||||
private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
|
||||
override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
|
||||
|
||||
private val _authenticationMethod =
|
||||
MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
|
||||
val authenticationMethod: StateFlow<AuthenticationMethodModel> =
|
||||
_authenticationMethod.asStateFlow()
|
||||
|
||||
private var isLockscreenEnabled = true
|
||||
private var failedAttemptCount = 0
|
||||
private var throttlingEndTimestamp = 0L
|
||||
private var credentialOverride: List<Any>? = null
|
||||
private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
|
||||
|
||||
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
|
||||
return authenticationMethod
|
||||
return authenticationMethod.value
|
||||
}
|
||||
|
||||
fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
|
||||
this.authenticationMethod = authenticationMethod
|
||||
onSecurityModeChanged(authenticationMethod.toSecurityMode())
|
||||
_authenticationMethod.value = authenticationMethod
|
||||
securityMode = authenticationMethod.toSecurityMode()
|
||||
}
|
||||
|
||||
fun overrideCredential(pin: List<Int>) {
|
||||
credentialOverride = pin
|
||||
}
|
||||
|
||||
override suspend fun isLockscreenEnabled(): Boolean {
|
||||
return isLockscreenEnabled
|
||||
}
|
||||
|
||||
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
|
||||
failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
|
||||
_isUnlocked.value = isSuccessful
|
||||
}
|
||||
|
||||
override suspend fun getPinLength(): Int {
|
||||
return (credentialOverride ?: listOf(1, 2, 3, 4)).size
|
||||
}
|
||||
|
||||
override fun setBypassEnabled(isBypassEnabled: Boolean) {
|
||||
_isBypassEnabled.value = isBypassEnabled
|
||||
}
|
||||
|
||||
override suspend fun getFailedAuthenticationAttemptCount(): Int {
|
||||
return failedAttemptCount
|
||||
}
|
||||
|
||||
override suspend fun getThrottlingEndTimestamp(): Long {
|
||||
return throttlingEndTimestamp
|
||||
}
|
||||
|
||||
override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
|
||||
_throttling.value = throttlingModel
|
||||
}
|
||||
|
||||
fun setUnlocked(isUnlocked: Boolean) {
|
||||
_isUnlocked.value = isUnlocked
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEFAULT_AUTHENTICATION_METHOD =
|
||||
AuthenticationMethodModel.Pin(listOf(1, 2, 3, 4), autoConfirm = false)
|
||||
fun setAutoConfirmEnabled(isEnabled: Boolean) {
|
||||
_isAutoConfirmEnabled.value = isEnabled
|
||||
}
|
||||
|
||||
fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
|
||||
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
|
||||
this.isLockscreenEnabled = isLockscreenEnabled
|
||||
}
|
||||
|
||||
override suspend fun setThrottleDuration(durationMs: Int) {
|
||||
throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
|
||||
}
|
||||
|
||||
override suspend fun checkCredential(
|
||||
credential: LockscreenCredential
|
||||
): AuthenticationResultModel {
|
||||
val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
|
||||
val isSuccessful =
|
||||
when {
|
||||
credential.type != getCurrentCredentialType(securityMode) -> false
|
||||
credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
|
||||
credential.isPin && credential.matches(expectedCredential)
|
||||
credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
|
||||
credential.isPassword && credential.matches(expectedCredential)
|
||||
credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
|
||||
credential.isPattern && credential.matches(expectedCredential)
|
||||
else -> error("Unexpected credential type ${credential.type}!")
|
||||
}
|
||||
|
||||
return if (
|
||||
isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
|
||||
) {
|
||||
AuthenticationResultModel(
|
||||
isSuccessful = isSuccessful,
|
||||
throttleDurationMs = 0,
|
||||
)
|
||||
} else {
|
||||
AuthenticationResultModel(
|
||||
isSuccessful = false,
|
||||
throttleDurationMs = THROTTLE_DURATION_MS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
|
||||
val PATTERN =
|
||||
listOf(
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
|
||||
AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
|
||||
)
|
||||
const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
|
||||
const val THROTTLE_DURATION_MS = 30000
|
||||
|
||||
private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
|
||||
return when (this) {
|
||||
is AuthenticationMethodModel.Pin -> SecurityMode.PIN
|
||||
is AuthenticationMethodModel.Password -> SecurityMode.Password
|
||||
@@ -58,5 +172,50 @@ class FakeAuthenticationRepository(
|
||||
is AuthenticationMethodModel.None -> SecurityMode.None
|
||||
}
|
||||
}
|
||||
|
||||
@LockPatternUtils.CredentialType
|
||||
private fun getCurrentCredentialType(
|
||||
securityMode: SecurityMode,
|
||||
): Int {
|
||||
return when (securityMode) {
|
||||
SecurityMode.PIN,
|
||||
SecurityMode.SimPin,
|
||||
SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN
|
||||
SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
|
||||
SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN
|
||||
SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE
|
||||
else -> error("Unsupported SecurityMode $securityMode!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
|
||||
return when (val credentialType = getCurrentCredentialType(securityMode)) {
|
||||
LockPatternUtils.CREDENTIAL_TYPE_PIN -> listOf(1, 2, 3, 4)
|
||||
LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
|
||||
LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
|
||||
else -> error("Unsupported credential type $credentialType!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return when {
|
||||
isPin ->
|
||||
credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential
|
||||
isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential
|
||||
isPattern ->
|
||||
credential.contentEquals(
|
||||
LockPatternUtils.patternToByteArray(
|
||||
expectedCredential as List<LockPatternView.Cell>
|
||||
)
|
||||
)
|
||||
else -> error("Unsupported credential type $type!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
|
||||
List<LockPatternView.Cell> {
|
||||
return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,10 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
_statusBarState.value = state
|
||||
}
|
||||
|
||||
fun setKeyguardUnlocked(isUnlocked: Boolean) {
|
||||
_isKeyguardUnlocked.value = isUnlocked
|
||||
}
|
||||
|
||||
override fun isUdfpsSupported(): Boolean {
|
||||
return _isUdfpsSupported.value
|
||||
}
|
||||
|
||||
@@ -16,29 +16,30 @@
|
||||
|
||||
package com.android.systemui.scene
|
||||
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
||||
import android.content.pm.UserInfo
|
||||
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.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository.Companion.toSecurityMode
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.bouncer.data.repository.BouncerRepository
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import com.android.systemui.scene.data.repository.SceneContainerRepository
|
||||
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneContainerNames
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.currentTime
|
||||
|
||||
/**
|
||||
* Utilities for creating scene container framework related repositories, interactors, and
|
||||
@@ -48,22 +49,19 @@ import kotlinx.coroutines.test.TestScope
|
||||
class SceneTestUtils(
|
||||
test: SysuiTestCase,
|
||||
) {
|
||||
val testDispatcher: TestDispatcher by lazy { StandardTestDispatcher() }
|
||||
val testScope: TestScope by lazy { TestScope(testDispatcher) }
|
||||
private var securityMode: SecurityMode =
|
||||
FakeAuthenticationRepository.DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
val testScope = TestScope(testDispatcher)
|
||||
private val userRepository: UserRepository by lazy {
|
||||
FakeUserRepository().apply {
|
||||
val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
|
||||
setUserInfos(users)
|
||||
runBlocking { setSelectedUserInfo(users.first()) }
|
||||
}
|
||||
}
|
||||
|
||||
val authenticationRepository: FakeAuthenticationRepository by lazy {
|
||||
FakeAuthenticationRepository(
|
||||
delegate =
|
||||
AuthenticationRepositoryImpl(
|
||||
applicationScope = applicationScope(),
|
||||
getSecurityMode = { securityMode },
|
||||
backgroundDispatcher = testDispatcher,
|
||||
userRepository = FakeUserRepository(),
|
||||
lockPatternUtils = mock(),
|
||||
keyguardRepository = FakeKeyguardRepository(),
|
||||
),
|
||||
onSecurityModeChanged = { securityMode = it },
|
||||
currentTime = { testScope.currentTime },
|
||||
)
|
||||
}
|
||||
private val context = test.context
|
||||
@@ -105,7 +103,7 @@ class SceneTestUtils(
|
||||
)
|
||||
}
|
||||
|
||||
fun authenticationRepository(): AuthenticationRepository {
|
||||
fun authenticationRepository(): FakeAuthenticationRepository {
|
||||
return authenticationRepository
|
||||
}
|
||||
|
||||
@@ -115,6 +113,9 @@ class SceneTestUtils(
|
||||
return AuthenticationInteractor(
|
||||
applicationScope = applicationScope(),
|
||||
repository = repository,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
userRepository = userRepository,
|
||||
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ class SceneTestUtils(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CONTAINER_1 = "container1"
|
||||
const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
|
||||
const val CONTAINER_2 = "container2"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user