Add BouncerMessageRepository and BouncerMessageInteractor
Other changes: - Rename BouncerLog to BouncerTableLog - Make BouncerLog a normal LogBuffer and use it for logging bouncer state. Test: atest BouncerMessageRepositoryTest Test: atest BouncerMessageInteractorTest Bug: 275600559 Change-Id: Ifb505f6dc7ecd3b9385d2c3d58aa5011b6902abf
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.bouncer.data.repository
|
||||
|
||||
import android.hardware.biometrics.BiometricSourceType
|
||||
import android.hardware.biometrics.BiometricSourceType.FACE
|
||||
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
|
||||
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.TrustRepository
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
|
||||
/** Provide different sources of messages that needs to be shown on the bouncer. */
|
||||
interface BouncerMessageRepository {
|
||||
/**
|
||||
* Messages that are shown in response to the incorrect security attempts on the bouncer and
|
||||
* primary authentication method being locked out, along with countdown messages before primary
|
||||
* auth is active again.
|
||||
*/
|
||||
val primaryAuthMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/**
|
||||
* Help messages that are shown to the user on how to successfully perform authentication using
|
||||
* face.
|
||||
*/
|
||||
val faceAcquisitionMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/**
|
||||
* Help messages that are shown to the user on how to successfully perform authentication using
|
||||
* fingerprint.
|
||||
*/
|
||||
val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/** Custom message that is displayed when the bouncer is being shown to launch an app. */
|
||||
val customMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/**
|
||||
* Messages that are shown in response to biometric authentication attempts through face or
|
||||
* fingerprint.
|
||||
*/
|
||||
val biometricAuthMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/** Messages that are shown when certain auth flags are set. */
|
||||
val authFlagsMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/** Messages that are show after biometrics are locked out temporarily or permanently */
|
||||
val biometricLockedOutMessage: Flow<BouncerMessageModel?>
|
||||
|
||||
/** Set the value for [primaryAuthMessage] */
|
||||
fun setPrimaryAuthMessage(value: BouncerMessageModel?)
|
||||
|
||||
/** Set the value for [faceAcquisitionMessage] */
|
||||
fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
|
||||
/** Set the value for [fingerprintAcquisitionMessage] */
|
||||
fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
|
||||
|
||||
/** Set the value for [customMessage] */
|
||||
fun setCustomMessage(value: BouncerMessageModel?)
|
||||
|
||||
/**
|
||||
* Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
|
||||
* [fingerprintAcquisitionMessage] & [customMessage]
|
||||
*/
|
||||
fun clearMessage()
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class BouncerMessageRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
trustRepository: TrustRepository,
|
||||
biometricSettingsRepository: BiometricSettingsRepository,
|
||||
updateMonitor: KeyguardUpdateMonitor,
|
||||
private val bouncerMessageFactory: BouncerMessageFactory,
|
||||
private val userRepository: UserRepository,
|
||||
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
|
||||
) : BouncerMessageRepository {
|
||||
|
||||
private val isFaceEnrolledAndEnabled =
|
||||
and(
|
||||
biometricSettingsRepository.isFaceAuthenticationEnabled,
|
||||
biometricSettingsRepository.isFaceEnrolled
|
||||
)
|
||||
|
||||
private val isFingerprintEnrolledAndEnabled =
|
||||
and(
|
||||
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
|
||||
biometricSettingsRepository.isFingerprintEnrolled
|
||||
)
|
||||
|
||||
private val isAnyBiometricsEnabledAndEnrolled =
|
||||
or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
|
||||
|
||||
private val authFlagsBasedPromptReason: Flow<Int> =
|
||||
combine(
|
||||
biometricSettingsRepository.authenticationFlags,
|
||||
trustRepository.isCurrentUserTrustManaged,
|
||||
isAnyBiometricsEnabledAndEnrolled,
|
||||
::Triple
|
||||
)
|
||||
.map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
|
||||
val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
|
||||
return@map if (
|
||||
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
|
||||
) {
|
||||
PROMPT_REASON_RESTART
|
||||
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
|
||||
PROMPT_REASON_TIMEOUT
|
||||
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
|
||||
PROMPT_REASON_DEVICE_ADMIN
|
||||
} else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
|
||||
PROMPT_REASON_TRUSTAGENT_EXPIRED
|
||||
} else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
|
||||
PROMPT_REASON_TRUSTAGENT_EXPIRED
|
||||
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
|
||||
PROMPT_REASON_USER_REQUEST
|
||||
} else if (
|
||||
trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
|
||||
) {
|
||||
PROMPT_REASON_PREPARE_FOR_UPDATE
|
||||
} else if (
|
||||
trustOrBiometricsAvailable &&
|
||||
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
|
||||
) {
|
||||
PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
|
||||
} else {
|
||||
PROMPT_REASON_NONE
|
||||
}
|
||||
}
|
||||
|
||||
private val biometricAuthReason: Flow<Int> =
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : KeyguardUpdateMonitorCallback() {
|
||||
override fun onBiometricAuthFailed(
|
||||
biometricSourceType: BiometricSourceType?
|
||||
) {
|
||||
val promptReason =
|
||||
if (biometricSourceType == FINGERPRINT)
|
||||
PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
|
||||
else if (
|
||||
biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
|
||||
) {
|
||||
PROMPT_REASON_INCORRECT_FACE_INPUT
|
||||
} else PROMPT_REASON_NONE
|
||||
trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
|
||||
}
|
||||
|
||||
override fun onBiometricsCleared() {
|
||||
trySendWithFailureLogging(
|
||||
PROMPT_REASON_NONE,
|
||||
TAG,
|
||||
"onBiometricsCleared"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBiometricAcquired(
|
||||
biometricSourceType: BiometricSourceType?,
|
||||
acquireInfo: Int
|
||||
) {
|
||||
trySendWithFailureLogging(
|
||||
PROMPT_REASON_NONE,
|
||||
TAG,
|
||||
"clearBiometricPrompt for new auth session."
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBiometricAuthenticated(
|
||||
userId: Int,
|
||||
biometricSourceType: BiometricSourceType?,
|
||||
isStrongBiometric: Boolean
|
||||
) {
|
||||
trySendWithFailureLogging(
|
||||
PROMPT_REASON_NONE,
|
||||
TAG,
|
||||
"onBiometricAuthenticated"
|
||||
)
|
||||
}
|
||||
}
|
||||
updateMonitor.registerCallback(callback)
|
||||
awaitClose { updateMonitor.removeCallback(callback) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
|
||||
|
||||
private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
|
||||
|
||||
private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
|
||||
_fingerprintAcquisitionMessage
|
||||
|
||||
private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val customMessage: Flow<BouncerMessageModel?> = _customMessage
|
||||
|
||||
override val biometricAuthMessage: Flow<BouncerMessageModel?> =
|
||||
biometricAuthReason
|
||||
.map {
|
||||
if (it == PROMPT_REASON_NONE) null
|
||||
else
|
||||
bouncerMessageFactory.createFromPromptReason(
|
||||
it,
|
||||
userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
}
|
||||
.onStart { emit(null) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val authFlagsMessage: Flow<BouncerMessageModel?> =
|
||||
authFlagsBasedPromptReason
|
||||
.map {
|
||||
if (it == PROMPT_REASON_NONE) null
|
||||
else
|
||||
bouncerMessageFactory.createFromPromptReason(
|
||||
it,
|
||||
userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
}
|
||||
.onStart { emit(null) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
// TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
|
||||
// has been launched.
|
||||
private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : KeyguardUpdateMonitorCallback() {
|
||||
override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
|
||||
if (biometricSourceType == FACE) {
|
||||
trySendWithFailureLogging(
|
||||
updateMonitor.isFaceLockedOut,
|
||||
TAG,
|
||||
"face lock out state changed."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateMonitor.registerCallback(callback)
|
||||
trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
|
||||
awaitClose { updateMonitor.removeCallback(callback) }
|
||||
}
|
||||
|
||||
override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
|
||||
combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
|
||||
return@combine if (fp) {
|
||||
bouncerMessageFactory.createFromPromptReason(
|
||||
PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
|
||||
userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
} else if (face) {
|
||||
bouncerMessageFactory.createFromPromptReason(
|
||||
PROMPT_REASON_FACE_LOCKED_OUT,
|
||||
userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
|
||||
_primaryAuthMessage.value = value
|
||||
}
|
||||
|
||||
override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
|
||||
_faceAcquisitionMessage.value = value
|
||||
}
|
||||
|
||||
override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
|
||||
_fingerprintAcquisitionMessage.value = value
|
||||
}
|
||||
|
||||
override fun setCustomMessage(value: BouncerMessageModel?) {
|
||||
_customMessage.value = value
|
||||
}
|
||||
|
||||
override fun clearMessage() {
|
||||
_fingerprintAcquisitionMessage.value = null
|
||||
_faceAcquisitionMessage.value = null
|
||||
_primaryAuthMessage.value = null
|
||||
_customMessage.value = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "BouncerDetailedMessageRepository"
|
||||
}
|
||||
}
|
||||
|
||||
private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
|
||||
flow.combine(anotherFlow) { a, b -> a && b }
|
||||
|
||||
private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
|
||||
flow.combine(anotherFlow) { a, b -> a || b }
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.bouncer.domain.interactor
|
||||
|
||||
import android.os.CountDownTimer
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
|
||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
|
||||
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
|
||||
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@SysUISingleton
|
||||
class BouncerMessageInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
private val repository: BouncerMessageRepository,
|
||||
private val factory: BouncerMessageFactory,
|
||||
private val userRepository: UserRepository,
|
||||
private val countDownTimerUtil: CountDownTimerUtil,
|
||||
private val featureFlags: FeatureFlags,
|
||||
) {
|
||||
fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
val callback =
|
||||
object : CountDownTimerCallback {
|
||||
override fun onFinish() {
|
||||
repository.clearMessage()
|
||||
}
|
||||
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
|
||||
val message =
|
||||
factory.createFromPromptReason(
|
||||
reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
|
||||
userId = userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
message?.message?.animate = false
|
||||
message?.message?.formatterArgs =
|
||||
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
|
||||
repository.setPrimaryAuthMessage(message)
|
||||
}
|
||||
}
|
||||
countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
|
||||
}
|
||||
|
||||
fun onPrimaryAuthIncorrectAttempt() {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.setPrimaryAuthMessage(
|
||||
factory.createFromPromptReason(
|
||||
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
|
||||
userRepository.getSelectedUserInfo().id
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun setFingerprintAcquisitionMessage(value: String?) {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.setFingerprintAcquisitionMessage(
|
||||
if (value != null) {
|
||||
factory.createFromString(secondaryMsg = value)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setFaceAcquisitionMessage(value: String?) {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.setFaceAcquisitionMessage(
|
||||
if (value != null) {
|
||||
factory.createFromString(secondaryMsg = value)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setCustomMessage(value: String?) {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.setCustomMessage(
|
||||
if (value != null) {
|
||||
factory.createFromString(secondaryMsg = value)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun onPrimaryBouncerUserInput() {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.clearMessage()
|
||||
}
|
||||
|
||||
fun onBouncerBeingHidden() {
|
||||
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
|
||||
|
||||
repository.clearMessage()
|
||||
}
|
||||
|
||||
private fun firstNonNullMessage(
|
||||
oneMessageModel: Flow<BouncerMessageModel?>,
|
||||
anotherMessageModel: Flow<BouncerMessageModel?>
|
||||
): Flow<BouncerMessageModel?> {
|
||||
return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
|
||||
}
|
||||
|
||||
// Null if feature flag is enabled which gets ignored always or empty bouncer message model that
|
||||
// always maps to an empty string.
|
||||
private fun nullOrEmptyMessage() =
|
||||
flowOf(
|
||||
if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
|
||||
else factory.createFromString("", "")
|
||||
)
|
||||
|
||||
val bouncerMessage =
|
||||
listOf(
|
||||
nullOrEmptyMessage(),
|
||||
repository.primaryAuthMessage,
|
||||
repository.biometricAuthMessage,
|
||||
repository.fingerprintAcquisitionMessage,
|
||||
repository.faceAcquisitionMessage,
|
||||
repository.customMessage,
|
||||
repository.authFlagsMessage,
|
||||
repository.biometricLockedOutMessage,
|
||||
userRepository.selectedUserInfo.map {
|
||||
factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
|
||||
},
|
||||
)
|
||||
.reduce(::firstNonNullMessage)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
interface CountDownTimerCallback {
|
||||
fun onFinish()
|
||||
fun onTick(millisUntilFinished: Long)
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
open class CountDownTimerUtil @Inject constructor() {
|
||||
|
||||
/**
|
||||
* Start a new count down timer that runs for [millisInFuture] with a tick every
|
||||
* [millisInterval]
|
||||
*/
|
||||
fun startNewTimer(
|
||||
millisInFuture: Long,
|
||||
millisInterval: Long,
|
||||
callback: CountDownTimerCallback,
|
||||
): CountDownTimer {
|
||||
return object : CountDownTimer(millisInFuture, millisInterval) {
|
||||
override fun onFinish() = callback.onFinish()
|
||||
|
||||
override fun onTick(millisUntilFinished: Long) =
|
||||
callback.onTick(millisUntilFinished)
|
||||
}
|
||||
.start()
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
|
||||
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
|
||||
import com.android.systemui.log.dagger.BouncerLog
|
||||
import com.android.systemui.log.dagger.BouncerTableLog
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.log.table.logDiffsForTable
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
@@ -105,7 +105,7 @@ class KeyguardBouncerRepositoryImpl
|
||||
constructor(
|
||||
private val clock: SystemClock,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
@BouncerLog private val buffer: TableLogBuffer,
|
||||
@BouncerTableLog private val buffer: TableLogBuffer,
|
||||
) : KeyguardBouncerRepository {
|
||||
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
|
||||
private val _primaryBouncerShow = MutableStateFlow(false)
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.keyguard.data.repository
|
||||
|
||||
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
|
||||
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepositoryImpl
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
||||
@@ -46,5 +48,8 @@ interface KeyguardRepositoryModule {
|
||||
@Binds
|
||||
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
|
||||
|
||||
@Binds
|
||||
fun bouncerMessageRepository(impl: BouncerMessageRepositoryImpl): BouncerMessageRepository
|
||||
|
||||
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
|
||||
}
|
||||
|
||||
@@ -47,9 +47,6 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) {
|
||||
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
|
||||
)
|
||||
|
||||
val primaryAuthRequiredAfterLockout =
|
||||
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
|
||||
|
||||
val primaryAuthRequiredForUnattendedUpdate =
|
||||
containsFlag(
|
||||
flag,
|
||||
|
||||
@@ -38,7 +38,8 @@ data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationS
|
||||
object FailedAuthenticationStatus : AuthenticationStatus()
|
||||
|
||||
/** Face authentication error message */
|
||||
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() {
|
||||
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
|
||||
AuthenticationStatus() {
|
||||
/**
|
||||
* Method that checks if [msgId] is a lockout error. A lockout error means that face
|
||||
* authentication is locked out.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.log
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import com.android.systemui.log.dagger.BouncerLog
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "BouncerLog"
|
||||
|
||||
/**
|
||||
* Helper class for logging for classes in the [com.android.systemui.keyguard.bouncer] package.
|
||||
*
|
||||
* To enable logcat echoing for an entire buffer:
|
||||
* ```
|
||||
* adb shell settings put global systemui/buffer/BouncerLog <logLevel>
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@SysUISingleton
|
||||
class BouncerLogger @Inject constructor(@BouncerLog private val buffer: LogBuffer) {
|
||||
fun startBouncerMessageInteractor() {
|
||||
buffer.log(
|
||||
TAG,
|
||||
LogLevel.DEBUG,
|
||||
"Starting BouncerMessageInteractor.bouncerMessage collector"
|
||||
)
|
||||
}
|
||||
|
||||
fun bouncerMessageUpdated(bouncerMsg: BouncerMessageModel?) {
|
||||
buffer.log(
|
||||
TAG,
|
||||
LogLevel.DEBUG,
|
||||
{
|
||||
int1 = bouncerMsg?.message?.messageResId ?: -1
|
||||
str1 = bouncerMsg?.message?.message
|
||||
int2 = bouncerMsg?.secondaryMessage?.messageResId ?: -1
|
||||
str2 = bouncerMsg?.secondaryMessage?.message
|
||||
},
|
||||
{ "Bouncer message update received: $int1, $str1, $int2, $str2" }
|
||||
)
|
||||
}
|
||||
|
||||
fun bindingBouncerMessageView() {
|
||||
buffer.log(TAG, LogLevel.DEBUG, "Binding BouncerMessageView")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
package com.android.systemui.log.dagger
|
||||
|
||||
import java.lang.annotation.Documented
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/** Logger for the primary and alternative bouncers. */
|
||||
@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerLog
|
||||
/** A [com.android.systemui.log.LogBuffer] for bouncer and its child views. */
|
||||
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BouncerLog()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.log.dagger
|
||||
|
||||
import java.lang.annotation.Documented
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/** Logger for the primary and alternative bouncers. */
|
||||
@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerTableLog
|
||||
@@ -393,6 +393,17 @@ public class LogModule {
|
||||
return factory.create("DeviceEntryFaceAuthRepositoryLog", 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link LogBuffer} for use by classes in the
|
||||
* {@link com.android.systemui.keyguard.bouncer} package.
|
||||
*/
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@BouncerLog
|
||||
public static LogBuffer provideBouncerLog(LogBufferFactory factory) {
|
||||
return factory.create("BouncerLog", 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
|
||||
*/
|
||||
@@ -416,9 +427,9 @@ public class LogModule {
|
||||
/** Provides a logging buffer for the primary bouncer. */
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@BouncerLog
|
||||
@BouncerTableLog
|
||||
public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
|
||||
return factory.create("BouncerLog", 250);
|
||||
return factory.create("BouncerTableLog", 250);
|
||||
}
|
||||
|
||||
/** Provides a table logging buffer for the Monitor. */
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.bouncer.data.repository
|
||||
|
||||
import android.content.pm.UserInfo
|
||||
import android.hardware.biometrics.BiometricSourceType
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
|
||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.R.string.keyguard_enter_pin
|
||||
import com.android.systemui.R.string.kg_prompt_after_dpm_lock
|
||||
import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
|
||||
import com.android.systemui.R.string.kg_prompt_auth_timeout
|
||||
import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
|
||||
import com.android.systemui.R.string.kg_prompt_reason_restart_pin
|
||||
import com.android.systemui.R.string.kg_prompt_unattended_update
|
||||
import com.android.systemui.R.string.kg_trust_agent_disabled
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.Message
|
||||
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
|
||||
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BouncerMessageRepositoryTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
|
||||
@Mock private lateinit var securityModel: KeyguardSecurityModel
|
||||
@Captor
|
||||
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
|
||||
|
||||
private lateinit var underTest: BouncerMessageRepository
|
||||
private lateinit var trustRepository: FakeTrustRepository
|
||||
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
|
||||
private lateinit var userRepository: FakeUserRepository
|
||||
private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
|
||||
private lateinit var testScope: TestScope
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
trustRepository = FakeTrustRepository()
|
||||
biometricSettingsRepository = FakeBiometricSettingsRepository()
|
||||
userRepository = FakeUserRepository()
|
||||
userRepository.setUserInfos(listOf(PRIMARY_USER))
|
||||
fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
|
||||
testScope = TestScope()
|
||||
|
||||
whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
|
||||
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
|
||||
underTest =
|
||||
BouncerMessageRepositoryImpl(
|
||||
trustRepository = trustRepository,
|
||||
biometricSettingsRepository = biometricSettingsRepository,
|
||||
updateMonitor = updateMonitor,
|
||||
bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
|
||||
userRepository = userRepository,
|
||||
fingerprintAuthRepository = fingerprintRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setCustomMessage_propagatesState() =
|
||||
testScope.runTest {
|
||||
underTest.setCustomMessage(message("not empty"))
|
||||
|
||||
val customMessage = collectLastValue(underTest.customMessage)
|
||||
|
||||
assertThat(customMessage()).isEqualTo(message("not empty"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFaceMessage_propagatesState() =
|
||||
testScope.runTest {
|
||||
underTest.setFaceAcquisitionMessage(message("not empty"))
|
||||
|
||||
val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
|
||||
|
||||
assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFpMessage_propagatesState() =
|
||||
testScope.runTest {
|
||||
underTest.setFingerprintAcquisitionMessage(message("not empty"))
|
||||
|
||||
val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
|
||||
|
||||
assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setPrimaryAuthMessage_propagatesState() =
|
||||
testScope.runTest {
|
||||
underTest.setPrimaryAuthMessage(message("not empty"))
|
||||
|
||||
val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
|
||||
|
||||
assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun biometricAuthMessage_propagatesBiometricAuthMessages() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
|
||||
runCurrent()
|
||||
|
||||
verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
|
||||
|
||||
updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
|
||||
|
||||
assertThat(biometricAuthMessage())
|
||||
.isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
|
||||
|
||||
updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
|
||||
|
||||
assertThat(biometricAuthMessage())
|
||||
.isEqualTo(
|
||||
message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
|
||||
)
|
||||
|
||||
updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
|
||||
|
||||
assertThat(biometricAuthMessage()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onFaceLockout_propagatesState() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
|
||||
runCurrent()
|
||||
verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
|
||||
|
||||
whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
|
||||
updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
|
||||
|
||||
assertThat(lockoutMessage())
|
||||
.isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
|
||||
|
||||
whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
|
||||
updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
|
||||
assertThat(lockoutMessage()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onFingerprintLockout_propagatesState() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
|
||||
runCurrent()
|
||||
|
||||
fingerprintRepository.setLockedOut(true)
|
||||
|
||||
assertThat(lockedOutMessage())
|
||||
.isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
|
||||
|
||||
fingerprintRepository.setLockedOut(false)
|
||||
assertThat(lockedOutMessage()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
trustRepository.setCurrentUserTrustManaged(false)
|
||||
biometricSettingsRepository.setFaceEnrolled(false)
|
||||
biometricSettingsRepository.setFingerprintEnrolled(false)
|
||||
|
||||
verifyMessagesForAuthFlag(
|
||||
STRONG_AUTH_NOT_REQUIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
|
||||
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
biometricSettingsRepository.setFaceEnrolled(false)
|
||||
biometricSettingsRepository.setFingerprintEnrolled(false)
|
||||
|
||||
trustRepository.setCurrentUserTrustManaged(true)
|
||||
|
||||
verifyMessagesForAuthFlag(
|
||||
STRONG_AUTH_NOT_REQUIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_BOOT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
|
||||
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
|
||||
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
|
||||
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
|
||||
Pair(keyguard_enter_pin, kg_trust_agent_disabled),
|
||||
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
|
||||
Pair(keyguard_enter_pin, kg_trust_agent_disabled),
|
||||
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
|
||||
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
|
||||
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
|
||||
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
trustRepository.setCurrentUserTrustManaged(false)
|
||||
biometricSettingsRepository.setFingerprintEnrolled(false)
|
||||
|
||||
biometricSettingsRepository.setIsFaceAuthEnabled(true)
|
||||
biometricSettingsRepository.setFaceEnrolled(true)
|
||||
|
||||
verifyMessagesForAuthFlag(
|
||||
STRONG_AUTH_NOT_REQUIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_BOOT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
|
||||
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
|
||||
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
|
||||
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
|
||||
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
|
||||
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
|
||||
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
|
||||
testScope.runTest {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
trustRepository.setCurrentUserTrustManaged(false)
|
||||
biometricSettingsRepository.setIsFaceAuthEnabled(false)
|
||||
biometricSettingsRepository.setFaceEnrolled(false)
|
||||
|
||||
biometricSettingsRepository.setFingerprintEnrolled(true)
|
||||
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
|
||||
|
||||
verifyMessagesForAuthFlag(
|
||||
STRONG_AUTH_NOT_REQUIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
|
||||
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
|
||||
STRONG_AUTH_REQUIRED_AFTER_BOOT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
|
||||
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
|
||||
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
|
||||
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
|
||||
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
|
||||
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
|
||||
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
|
||||
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
|
||||
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
|
||||
)
|
||||
}
|
||||
|
||||
private fun TestScope.verifyMessagesForAuthFlag(
|
||||
vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
|
||||
) {
|
||||
val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
|
||||
|
||||
authFlagToExpectedMessages.forEach { (flag, messagePair) ->
|
||||
biometricSettingsRepository.setAuthenticationFlags(
|
||||
AuthenticationFlags(PRIMARY_USER_ID, flag)
|
||||
)
|
||||
|
||||
assertThat(authFlagsMessage())
|
||||
.isEqualTo(messagePair?.let { message(it.first, it.second) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
|
||||
return BouncerMessageModel(
|
||||
message = Message(messageResId = primaryResId),
|
||||
secondaryMessage = Message(messageResId = secondaryResId)
|
||||
)
|
||||
}
|
||||
private fun message(value: String): BouncerMessageModel {
|
||||
return BouncerMessageModel(message = Message(message = value))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PRIMARY_USER_ID = 0
|
||||
private val PRIMARY_USER =
|
||||
UserInfo(
|
||||
/* id= */ PRIMARY_USER_ID,
|
||||
/* name= */ "primary user",
|
||||
/* flags= */ UserInfo.FLAG_PRIMARY
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.bouncer.domain.interactor
|
||||
|
||||
import android.content.pm.UserInfo
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.R.string.keyguard_enter_pin
|
||||
import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.FlowValue
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.Message
|
||||
import com.android.systemui.keyguard.data.repository.FakeBouncerMessageRepository
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.util.mockito.KotlinArgumentCaptor
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BouncerMessageInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var securityModel: KeyguardSecurityModel
|
||||
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
|
||||
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
|
||||
private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
|
||||
private lateinit var underTest: BouncerMessageInteractor
|
||||
private lateinit var repository: FakeBouncerMessageRepository
|
||||
private lateinit var userRepository: FakeUserRepository
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
repository = FakeBouncerMessageRepository()
|
||||
userRepository = FakeUserRepository()
|
||||
userRepository.setUserInfos(listOf(PRIMARY_USER))
|
||||
testScope = TestScope()
|
||||
countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
|
||||
|
||||
allowTestableLooperAsMainThread()
|
||||
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
|
||||
whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
|
||||
}
|
||||
|
||||
suspend fun TestScope.init() {
|
||||
userRepository.setSelectedUserInfo(PRIMARY_USER)
|
||||
val featureFlags = FakeFeatureFlags()
|
||||
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||
underTest =
|
||||
BouncerMessageInteractor(
|
||||
repository = repository,
|
||||
factory = BouncerMessageFactory(updateMonitor, securityModel),
|
||||
userRepository = userRepository,
|
||||
countDownTimerUtil = countDownTimerUtil,
|
||||
featureFlags = featureFlags
|
||||
)
|
||||
bouncerMessage = collectLastValue(underTest.bouncerMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
underTest.onPrimaryAuthIncorrectAttempt()
|
||||
|
||||
assertThat(repository.primaryAuthMessage).isNotNull()
|
||||
assertThat(
|
||||
context.resources.getString(
|
||||
repository.primaryAuthMessage.value!!.message!!.messageResId!!
|
||||
)
|
||||
)
|
||||
.isEqualTo("Wrong PIN. Try again.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
repository.setCustomMessage(message("not empty"))
|
||||
repository.setFaceAcquisitionMessage(message("not empty"))
|
||||
repository.setFingerprintAcquisitionMessage(message("not empty"))
|
||||
repository.setPrimaryAuthMessage(message("not empty"))
|
||||
|
||||
underTest.onPrimaryBouncerUserInput()
|
||||
|
||||
assertThat(repository.customMessage.value).isNull()
|
||||
assertThat(repository.faceAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.primaryAuthMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
repository.setCustomMessage(message("not empty"))
|
||||
repository.setFaceAcquisitionMessage(message("not empty"))
|
||||
repository.setFingerprintAcquisitionMessage(message("not empty"))
|
||||
repository.setPrimaryAuthMessage(message("not empty"))
|
||||
|
||||
underTest.onBouncerBeingHidden()
|
||||
|
||||
assertThat(repository.customMessage.value).isNull()
|
||||
assertThat(repository.faceAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.primaryAuthMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setCustomMessage_setsRepositoryValue() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
|
||||
underTest.setCustomMessage("not empty")
|
||||
|
||||
assertThat(repository.customMessage.value)
|
||||
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
|
||||
|
||||
underTest.setCustomMessage(null)
|
||||
assertThat(repository.customMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFaceMessage_setsRepositoryValue() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
|
||||
underTest.setFaceAcquisitionMessage("not empty")
|
||||
|
||||
assertThat(repository.faceAcquisitionMessage.value)
|
||||
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
|
||||
|
||||
underTest.setFaceAcquisitionMessage(null)
|
||||
assertThat(repository.faceAcquisitionMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFingerprintMessage_setsRepositoryValue() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
|
||||
underTest.setFingerprintAcquisitionMessage("not empty")
|
||||
|
||||
assertThat(repository.fingerprintAcquisitionMessage.value)
|
||||
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
|
||||
|
||||
underTest.setFingerprintAcquisitionMessage(null)
|
||||
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
|
||||
underTest.onPrimaryAuthLockedOut(3)
|
||||
|
||||
verify(countDownTimerUtil)
|
||||
.startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
|
||||
|
||||
countDownTimerCallback.value.onTick(2000L)
|
||||
|
||||
val primaryMessage = repository.primaryAuthMessage.value!!.message!!
|
||||
assertThat(primaryMessage.messageResId!!)
|
||||
.isEqualTo(kg_too_many_failed_attempts_countdown)
|
||||
assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
repository.setCustomMessage(message("not empty"))
|
||||
repository.setFaceAcquisitionMessage(message("not empty"))
|
||||
repository.setFingerprintAcquisitionMessage(message("not empty"))
|
||||
repository.setPrimaryAuthMessage(message("not empty"))
|
||||
|
||||
underTest.onPrimaryAuthLockedOut(3)
|
||||
|
||||
verify(countDownTimerUtil)
|
||||
.startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
|
||||
|
||||
countDownTimerCallback.value.onFinish()
|
||||
|
||||
assertThat(repository.customMessage.value).isNull()
|
||||
assertThat(repository.faceAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
|
||||
assertThat(repository.primaryAuthMessage.value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bouncerMessage_hasPriorityOrderOfMessages() =
|
||||
testScope.runTest {
|
||||
init()
|
||||
repository.setBiometricAuthMessage(message("biometric message"))
|
||||
repository.setFaceAcquisitionMessage(message("face acquisition message"))
|
||||
repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
|
||||
repository.setPrimaryAuthMessage(message("primary auth message"))
|
||||
repository.setAuthFlagsMessage(message("auth flags message"))
|
||||
repository.setBiometricLockedOutMessage(message("biometrics locked out"))
|
||||
repository.setCustomMessage(message("custom message"))
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
|
||||
|
||||
repository.setPrimaryAuthMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
|
||||
|
||||
repository.setBiometricAuthMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
|
||||
|
||||
repository.setFingerprintAcquisitionMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
|
||||
|
||||
repository.setFaceAcquisitionMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("custom message"))
|
||||
|
||||
repository.setCustomMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
|
||||
|
||||
repository.setAuthFlagsMessage(null)
|
||||
|
||||
assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
|
||||
|
||||
repository.setBiometricLockedOutMessage(null)
|
||||
|
||||
// sets the default message if everything else is null
|
||||
assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
|
||||
}
|
||||
|
||||
private fun message(value: String): BouncerMessageModel {
|
||||
return BouncerMessageModel(message = Message(message = value))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PRIMARY_USER_ID = 0
|
||||
private val PRIMARY_USER =
|
||||
UserInfo(
|
||||
/* id= */ PRIMARY_USER_ID,
|
||||
/* name= */ "primary user",
|
||||
/* flags= */ UserInfo.FLAG_PRIMARY
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.data.repository
|
||||
|
||||
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
|
||||
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakeBouncerMessageRepository : BouncerMessageRepository {
|
||||
private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _primaryAuthMessage
|
||||
|
||||
private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _faceAcquisitionMessage
|
||||
private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _fingerprintAcquisitionMessage
|
||||
private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val customMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _customMessage
|
||||
private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _biometricAuthMessage
|
||||
private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val authFlagsMessage: StateFlow<BouncerMessageModel?>
|
||||
get() = _authFlagsMessage
|
||||
|
||||
private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
|
||||
override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
|
||||
get() = _biometricLockedOutMessage
|
||||
|
||||
override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
|
||||
_primaryAuthMessage.value = value
|
||||
}
|
||||
|
||||
override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
|
||||
_faceAcquisitionMessage.value = value
|
||||
}
|
||||
|
||||
override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
|
||||
_fingerprintAcquisitionMessage.value = value
|
||||
}
|
||||
|
||||
override fun setCustomMessage(value: BouncerMessageModel?) {
|
||||
_customMessage.value = value
|
||||
}
|
||||
|
||||
fun setBiometricAuthMessage(value: BouncerMessageModel?) {
|
||||
_biometricAuthMessage.value = value
|
||||
}
|
||||
|
||||
fun setAuthFlagsMessage(value: BouncerMessageModel?) {
|
||||
_authFlagsMessage.value = value
|
||||
}
|
||||
|
||||
fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
|
||||
_biometricLockedOutMessage.value = value
|
||||
}
|
||||
|
||||
override fun clearMessage() {
|
||||
_primaryAuthMessage.value = null
|
||||
_faceAcquisitionMessage.value = null
|
||||
_fingerprintAcquisitionMessage.value = null
|
||||
_customMessage.value = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user