[flexiglass] Hooks up real app state and logic in keyguard (3)
- Connects the unlock logic to Flexiglass to let it unlock the device. Bug: 280883900 Test: Unit tests still pass. Test: manually verified that unlocking the device through the Flexiglass bouncer actually unlocks the actual device. Test: manually verified that locking the device using the power button (with "instantly lock device" setting both on and off) properly locks the device and moves back to the Lockscreen scene that shows the lock icon as locked. Test: verified that above with no lock screen, swipe method, PIN, pattern, and password methods. Change-Id: I1cfb71ab31cf2ddda54c8009656a58b3f64e6fa6
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,
|
||||
)
|
||||
@@ -530,6 +530,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
|
||||
}
|
||||
@@ -772,17 +778,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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,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