From 5fcf8a6cb433e4d0e49701f35ea629e6cb6950c7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 22 Nov 2024 17:13:08 +0100 Subject: [PATCH] change : confirm biometric before allowing biometric unlock. --- .../impl/DefaultLockScreenService.kt | 8 +- ...ricUnlock.kt => BiometricAuthenticator.kt} | 47 ++++---- ...er.kt => BiometricAuthenticatorManager.kt} | 17 ++- ...> DefaultBiometricAuthenticatorManager.kt} | 49 ++++++-- .../DefaultBiometricUnlockCallback.kt | 6 +- .../settings/LockScreenSettingsPresenter.kt | 18 ++- .../impl/setup/LockScreenSetupFlowNode.kt | 8 +- .../biometric/SetupBiometricPresenter.kt | 11 +- .../lockscreen/impl/unlock/PinUnlockHelper.kt | 10 +- .../impl/unlock/PinUnlockPresenter.kt | 10 +- .../lockscreen/impl/unlock/PinUnlockState.kt | 6 +- .../impl/unlock/PinUnlockStateProvider.kt | 6 +- .../impl/src/main/res/values/localazy.xml | 1 + .../biometric/FakeBiometricAuthenticator.kt | 16 +++ .../FakeBiometricAuthenticatorManager.kt | 39 ++++++ .../biometric/FakeBiometricUnlockManager.kt | 31 ----- .../LockScreenSettingsPresenterTest.kt | 112 ++++++++++++++++-- .../biometric/SetupBiometricPresenterTest.kt | 33 +++++- .../impl/unlock/PinUnlockPresenterTest.kt | 10 +- 19 files changed, 321 insertions(+), 117 deletions(-) rename features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/{BiometricUnlock.kt => BiometricAuthenticator.kt} (70%) rename features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/{BiometricUnlockManager.kt => BiometricAuthenticatorManager.kt} (53%) rename features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/{DefaultBiometricUnlockManager.kt => DefaultBiometricAuthenticatorManager.kt} (76%) create mode 100644 features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt create mode 100644 features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt delete mode 100644 features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt index 577e233164..a7cd2af9a9 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt @@ -10,7 +10,7 @@ package io.element.android.features.lockscreen.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.biometric.DefaultBiometricUnlockCallback import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback import io.element.android.features.lockscreen.impl.pin.PinCodeManager @@ -45,7 +45,7 @@ class DefaultLockScreenService @Inject constructor( private val coroutineScope: CoroutineScope, private val sessionObserver: SessionObserver, private val appForegroundStateService: AppForegroundStateService, - biometricUnlockManager: BiometricUnlockManager, + biometricAuthenticatorManager: BiometricAuthenticatorManager, ) : LockScreenService { private val _lockState = MutableStateFlow(LockScreenLockState.Unlocked) override val lockState: StateFlow = _lockState @@ -62,8 +62,8 @@ class DefaultLockScreenService @Inject constructor( _lockState.value = LockScreenLockState.Unlocked } }) - biometricUnlockManager.addCallback(object : DefaultBiometricUnlockCallback() { - override fun onBiometricUnlockSuccess() { + biometricAuthenticatorManager.addCallback(object : DefaultBiometricUnlockCallback() { + override fun onBiometricAuthenticationSuccess() { _lockState.value = LockScreenLockState.Unlocked coroutineScope.launch { lockScreenStore.resetCounter() diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt similarity index 70% rename from features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt rename to features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt index a922ff0ded..2981761b43 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlock.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt @@ -21,11 +21,11 @@ import timber.log.Timber import java.security.InvalidKeyException import javax.crypto.Cipher -interface BiometricUnlock { +interface BiometricAuthenticator { interface Callback { fun onBiometricSetupError() - fun onBiometricUnlockSuccess() - fun onBiometricUnlockFailed(error: Exception?) + fun onBiometricAuthenticationSuccess() + fun onBiometricAuthenticationFailed(error: Exception?) } sealed interface AuthenticationResult { @@ -38,23 +38,23 @@ interface BiometricUnlock { suspend fun authenticate(): AuthenticationResult } -class NoopBiometricUnlock : BiometricUnlock { +class NoopBiometricAuthentication : BiometricAuthenticator { override val isActive: Boolean = false override fun setup() = Unit - override suspend fun authenticate() = BiometricUnlock.AuthenticationResult.Failure() + override suspend fun authenticate() = BiometricAuthenticator.AuthenticationResult.Failure() } -class DefaultBiometricUnlock( +class DefaultBiometricAuthentication( private val activity: FragmentActivity, private val promptInfo: PromptInfo, private val secretKeyRepository: SecretKeyRepository, private val encryptionDecryptionService: EncryptionDecryptionService, private val keyAlias: String, - private val callbacks: List -) : BiometricUnlock { + private val callbacks: List +) : BiometricAuthenticator { override val isActive: Boolean = true - private lateinit var cryptoObject: CryptoObject + private var cryptoObject: CryptoObject? = null override fun setup() { try { @@ -67,11 +67,10 @@ class DefaultBiometricUnlock( } } - override suspend fun authenticate(): BiometricUnlock.AuthenticationResult { - if (!this::cryptoObject.isInitialized) { - return BiometricUnlock.AuthenticationResult.Failure() - } - val deferredAuthenticationResult = CompletableDeferred() + override suspend fun authenticate(): BiometricAuthenticator.AuthenticationResult { + val cryptoObject = cryptoObject ?: return BiometricAuthenticator.AuthenticationResult.Failure() + + val deferredAuthenticationResult = CompletableDeferred() val executor = ContextCompat.getMainExecutor(activity.baseContext) val callback = AuthenticationCallback(callbacks, deferredAuthenticationResult) val prompt = BiometricPrompt(activity, executor, callback) @@ -80,7 +79,7 @@ class DefaultBiometricUnlock( deferredAuthenticationResult.await() } catch (cancellation: CancellationException) { prompt.cancelAuthentication() - BiometricUnlock.AuthenticationResult.Failure(cancellation) + BiometricAuthenticator.AuthenticationResult.Failure(cancellation) } } @@ -91,30 +90,30 @@ class DefaultBiometricUnlock( } private class AuthenticationCallback( - private val callbacks: List, - private val deferredAuthenticationResult: CompletableDeferred, + private val callbacks: List, + private val deferredAuthenticationResult: CompletableDeferred, ) : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) val biometricUnlockError = BiometricUnlockError(errorCode, errString.toString()) - callbacks.forEach { it.onBiometricUnlockFailed(biometricUnlockError) } - deferredAuthenticationResult.complete(BiometricUnlock.AuthenticationResult.Failure(biometricUnlockError)) + callbacks.forEach { it.onBiometricAuthenticationFailed(biometricUnlockError) } + deferredAuthenticationResult.complete(BiometricAuthenticator.AuthenticationResult.Failure(biometricUnlockError)) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() - callbacks.forEach { it.onBiometricUnlockFailed(null) } + callbacks.forEach { it.onBiometricAuthenticationFailed(null) } } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) if (result.cryptoObject?.cipher.isValid()) { - callbacks.forEach { it.onBiometricUnlockSuccess() } - deferredAuthenticationResult.complete(BiometricUnlock.AuthenticationResult.Success) + callbacks.forEach { it.onBiometricAuthenticationSuccess() } + deferredAuthenticationResult.complete(BiometricAuthenticator.AuthenticationResult.Success) } else { val error = IllegalStateException("Invalid cipher") - callbacks.forEach { it.onBiometricUnlockFailed(error) } - deferredAuthenticationResult.complete(BiometricUnlock.AuthenticationResult.Failure()) + callbacks.forEach { it.onBiometricAuthenticationFailed(error) } + deferredAuthenticationResult.complete(BiometricAuthenticator.AuthenticationResult.Failure()) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt similarity index 53% rename from features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt rename to features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt index 6e625c8ce9..ce2375a1a8 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt @@ -9,7 +9,7 @@ package io.element.android.features.lockscreen.impl.biometric import androidx.compose.runtime.Composable -interface BiometricUnlockManager { +interface BiometricAuthenticatorManager { /** * If the device is secured for example with a pin, pattern or password. */ @@ -20,9 +20,18 @@ interface BiometricUnlockManager { */ val hasAvailableAuthenticator: Boolean - fun addCallback(callback: BiometricUnlock.Callback) - fun removeCallback(callback: BiometricUnlock.Callback) + fun addCallback(callback: BiometricAuthenticator.Callback) + fun removeCallback(callback: BiometricAuthenticator.Callback) + /** + * Remember a biometric authenticator ready for unlocking the app. + */ @Composable - fun rememberBiometricUnlock(): BiometricUnlock + fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator + + /** + * Remember a biometric authenticator ready for confirmation. + */ + @Composable + fun rememberConfirmBiometricAuthenticator(): BiometricAuthenticator } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt similarity index 76% rename from features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt rename to features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt index 4d74574beb..af2c37958c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.cryptography.api.SecretKeyRepository import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.util.concurrent.CopyOnWriteArrayList @@ -40,15 +41,15 @@ private const val SECRET_KEY_ALIAS = "elementx.SECRET_KEY_ALIAS_BIOMETRIC" @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -class DefaultBiometricUnlockManager @Inject constructor( +class DefaultBiometricAuthenticatorManager @Inject constructor( @ApplicationContext private val context: Context, private val lockScreenStore: LockScreenStore, private val lockScreenConfig: LockScreenConfig, private val encryptionDecryptionService: EncryptionDecryptionService, private val secretKeyRepository: SecretKeyRepository, private val coroutineScope: CoroutineScope, -) : BiometricUnlockManager { - private val callbacks = CopyOnWriteArrayList() +) : BiometricAuthenticatorManager { + private val callbacks = CopyOnWriteArrayList() private val biometricManager = BiometricManager.from(context) private val keyguardManager: KeyguardManager = context.getSystemService()!! @@ -85,16 +86,42 @@ class DefaultBiometricUnlockManager @Inject constructor( } @Composable - override fun rememberBiometricUnlock(): BiometricUnlock { + override fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator { val isBiometricAllowed by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState() val isAvailable by remember(lifecycleState) { - derivedStateOf { - isBiometricAllowed && hasAvailableAuthenticator - } + derivedStateOf { isBiometricAllowed && hasAvailableAuthenticator } } val promptTitle = stringResource(id = R.string.screen_app_lock_biometric_unlock_title_android) val promptNegative = stringResource(id = R.string.screen_app_lock_use_pin_android) + return rememberBiometricAuthenticator( + isAvailable = isAvailable, + promptTitle = promptTitle, + promptNegative = promptNegative, + ) + } + + @Composable + override fun rememberConfirmBiometricAuthenticator(): BiometricAuthenticator { + val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState() + val isAvailable by remember(lifecycleState) { + derivedStateOf { hasAvailableAuthenticator } + } + val promptTitle = stringResource(id = R.string.screen_app_lock_confirm_biometric_authentication_android) + val promptNegative = stringResource(id = CommonStrings.action_cancel) + return rememberBiometricAuthenticator( + isAvailable = isAvailable, + promptTitle = promptTitle, + promptNegative = promptNegative, + ) + } + + @Composable + private fun rememberBiometricAuthenticator( + isAvailable: Boolean, + promptTitle: String, + promptNegative: String, + ): BiometricAuthenticator { val activity = LocalContext.current.findFragmentActivity() return remember(isAvailable) { if (isAvailable && activity != null) { @@ -108,7 +135,7 @@ class DefaultBiometricUnlockManager @Inject constructor( setNegativeButtonText(promptNegative) setAllowedAuthenticators(authenticators) }.build() - DefaultBiometricUnlock( + DefaultBiometricAuthentication( activity = activity, promptInfo = promptInfo, secretKeyRepository = secretKeyRepository, @@ -117,16 +144,16 @@ class DefaultBiometricUnlockManager @Inject constructor( callbacks = callbacks + internalCallback ) } else { - NoopBiometricUnlock() + NoopBiometricAuthentication() } } } - override fun addCallback(callback: BiometricUnlock.Callback) { + override fun addCallback(callback: BiometricAuthenticator.Callback) { callbacks.add(callback) } - override fun removeCallback(callback: BiometricUnlock.Callback) { + override fun removeCallback(callback: BiometricAuthenticator.Callback) { callbacks.remove(callback) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt index 6511ead1a7..ec49129528 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt @@ -7,8 +7,8 @@ package io.element.android.features.lockscreen.impl.biometric -open class DefaultBiometricUnlockCallback : BiometricUnlock.Callback { +open class DefaultBiometricUnlockCallback : BiometricAuthenticator.Callback { override fun onBiometricSetupError() = Unit - override fun onBiometricUnlockSuccess() = Unit - override fun onBiometricUnlockFailed(error: Exception?) = Unit + override fun onBiometricAuthenticationSuccess() = Unit + override fun onBiometricAuthenticationFailed(error: Exception?) = Unit } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index 69b3e60dd6..a05ba1d567 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -15,7 +15,8 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.features.lockscreen.impl.LockScreenConfig -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.storage.LockScreenStore import io.element.android.libraries.architecture.Presenter @@ -27,7 +28,7 @@ class LockScreenSettingsPresenter @Inject constructor( private val lockScreenConfig: LockScreenConfig, private val pinCodeManager: PinCodeManager, private val lockScreenStore: LockScreenStore, - private val biometricUnlockManager: BiometricUnlockManager, + private val biometricAuthenticatorManager: BiometricAuthenticatorManager, private val coroutineScope: CoroutineScope, ) : Presenter { @Composable @@ -42,6 +43,8 @@ class LockScreenSettingsPresenter @Inject constructor( mutableStateOf(false) } + val biometricUnlock = biometricAuthenticatorManager.rememberConfirmBiometricAuthenticator() + fun handleEvents(event: LockScreenSettingsEvents) { when (event) { LockScreenSettingsEvents.CancelRemovePin -> showRemovePinConfirmation = false @@ -56,7 +59,14 @@ class LockScreenSettingsPresenter @Inject constructor( LockScreenSettingsEvents.OnRemovePin -> showRemovePinConfirmation = true LockScreenSettingsEvents.ToggleBiometricAllowed -> { coroutineScope.launch { - lockScreenStore.setIsBiometricUnlockAllowed(!isBiometricEnabled) + if (!isBiometricEnabled) { + biometricUnlock.setup() + if (biometricUnlock.authenticate() == BiometricAuthenticator.AuthenticationResult.Success) { + lockScreenStore.setIsBiometricUnlockAllowed(true) + } + } else { + lockScreenStore.setIsBiometricUnlockAllowed(false) + } } } } @@ -66,7 +76,7 @@ class LockScreenSettingsPresenter @Inject constructor( showRemovePinOption = showRemovePinOption, isBiometricEnabled = isBiometricEnabled, showRemovePinConfirmation = showRemovePinConfirmation, - showToggleBiometric = biometricUnlockManager.isDeviceSecured, + showToggleBiometric = biometricAuthenticatorManager.isDeviceSecured, eventSink = ::handleEvents ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt index 3ade948549..7a36d0705e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt @@ -20,6 +20,7 @@ import com.bumble.appyx.navmodel.backstack.operation.newRoot import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.setup.biometric.SetupBiometricNode @@ -35,6 +36,7 @@ class LockScreenSetupFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val pinCodeManager: PinCodeManager, + val biometricAuthenticatorManager: BiometricAuthenticatorManager, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Pin, @@ -61,7 +63,11 @@ class LockScreenSetupFlowNode @AssistedInject constructor( private val pinCodeManagerCallback = object : DefaultPinCodeManagerCallback() { override fun onPinCodeCreated() { - backstack.newRoot(NavTarget.Biometric) + if (biometricAuthenticatorManager.hasAvailableAuthenticator) { + backstack.newRoot(NavTarget.Biometric) + } else { + onSetupDone() + } } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt index da6a24337a..3689df78a7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt @@ -13,6 +13,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.storage.LockScreenStore import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.launch @@ -20,6 +22,7 @@ import javax.inject.Inject class SetupBiometricPresenter @Inject constructor( private val lockScreenStore: LockScreenStore, + private val biometricAuthenticatorManager: BiometricAuthenticatorManager, ) : Presenter { @Composable override fun present(): SetupBiometricState { @@ -28,12 +31,16 @@ class SetupBiometricPresenter @Inject constructor( } val coroutineScope = rememberCoroutineScope() + val biometricUnlock = biometricAuthenticatorManager.rememberConfirmBiometricAuthenticator() fun handleEvents(event: SetupBiometricEvents) { when (event) { SetupBiometricEvents.AllowBiometric -> coroutineScope.launch { - lockScreenStore.setIsBiometricUnlockAllowed(true) - isBiometricSetupDone = true + biometricUnlock.setup() + if (biometricUnlock.authenticate() == BiometricAuthenticator.AuthenticationResult.Success) { + lockScreenStore.setIsBiometricUnlockAllowed(true) + isBiometricSetupDone = true + } } SetupBiometricEvents.UsePin -> coroutineScope.launch { lockScreenStore.setIsBiometricUnlockAllowed(false) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt index 62346229e2..646e744e88 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt @@ -11,14 +11,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.biometric.DefaultBiometricUnlockCallback import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback import io.element.android.features.lockscreen.impl.pin.PinCodeManager import javax.inject.Inject class PinUnlockHelper @Inject constructor( - private val biometricUnlockManager: BiometricUnlockManager, + private val biometricAuthenticatorManager: BiometricAuthenticatorManager, private val pinCodeManager: PinCodeManager ) { @Composable @@ -26,7 +26,7 @@ class PinUnlockHelper @Inject constructor( val latestOnUnlock by rememberUpdatedState(onUnlock) DisposableEffect(Unit) { val biometricUnlockCallback = object : DefaultBiometricUnlockCallback() { - override fun onBiometricUnlockSuccess() { + override fun onBiometricAuthenticationSuccess() { latestOnUnlock() } } @@ -35,10 +35,10 @@ class PinUnlockHelper @Inject constructor( latestOnUnlock() } } - biometricUnlockManager.addCallback(biometricUnlockCallback) + biometricAuthenticatorManager.addCallback(biometricUnlockCallback) pinCodeManager.addCallback(pinCodeVerifiedCallback) onDispose { - biometricUnlockManager.removeCallback(biometricUnlockCallback) + biometricAuthenticatorManager.removeCallback(biometricUnlockCallback) pinCodeManager.removeCallback(pinCodeVerifiedCallback) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt index c561b36f0d..66a345f71f 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt @@ -15,8 +15,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.pin.PinCodeManager import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypadModel @@ -32,7 +32,7 @@ import javax.inject.Inject class PinUnlockPresenter @Inject constructor( private val pinCodeManager: PinCodeManager, - private val biometricUnlockManager: BiometricUnlockManager, + private val biometricAuthenticatorManager: BiometricAuthenticatorManager, private val logoutUseCase: LogoutUseCase, private val coroutineScope: CoroutineScope, private val pinUnlockHelper: PinUnlockHelper, @@ -56,12 +56,12 @@ class PinUnlockPresenter @Inject constructor( mutableStateOf>(AsyncAction.Uninitialized) } var biometricUnlockResult by remember { - mutableStateOf(null) + mutableStateOf(null) } val isUnlocked = remember { mutableStateOf(false) } - val biometricUnlock = biometricUnlockManager.rememberBiometricUnlock() + val biometricUnlock = biometricAuthenticatorManager.rememberUnlockBiometricAuthenticator() LaunchedEffect(Unit) { suspend { val pinCodeSize = pinCodeManager.getPinCodeSize() diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt index c0a28b9091..faed3ac394 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt @@ -7,7 +7,7 @@ package io.element.android.features.lockscreen.impl.unlock -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.libraries.architecture.AsyncAction @@ -21,7 +21,7 @@ data class PinUnlockState( val signOutAction: AsyncAction, val showBiometricUnlock: Boolean, val isUnlocked: Boolean, - val biometricUnlockResult: BiometricUnlock.AuthenticationResult?, + val biometricUnlockResult: BiometricAuthenticator.AuthenticationResult?, val eventSink: (PinUnlockEvents) -> Unit ) { val isSignOutPromptCancellable = when (remainingAttempts) { @@ -30,7 +30,7 @@ data class PinUnlockState( } val biometricUnlockErrorMessage = when { - biometricUnlockResult is BiometricUnlock.AuthenticationResult.Failure && + biometricUnlockResult is BiometricAuthenticator.AuthenticationResult.Failure && biometricUnlockResult.error is BiometricUnlockError && biometricUnlockResult.error.isAuthDisabledError -> { biometricUnlockResult.error.message diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt index fa6bca0ea8..f54846194d 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt @@ -9,7 +9,7 @@ package io.element.android.features.lockscreen.impl.unlock import androidx.biometric.BiometricPrompt import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError import io.element.android.features.lockscreen.impl.pin.model.PinEntry import io.element.android.libraries.architecture.AsyncAction @@ -25,7 +25,7 @@ open class PinUnlockStateProvider : PreviewParameterProvider { aPinUnlockState(showBiometricUnlock = false), aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0), aPinUnlockState(signOutAction = AsyncAction.Loading), - aPinUnlockState(biometricUnlockResult = BiometricUnlock.AuthenticationResult.Failure( + aPinUnlockState(biometricUnlockResult = BiometricAuthenticator.AuthenticationResult.Failure( BiometricUnlockError(BiometricPrompt.ERROR_LOCKOUT, "Biometric auth disabled") )), ) @@ -37,7 +37,7 @@ fun aPinUnlockState( showWrongPinTitle: Boolean = false, showSignOutPrompt: Boolean = false, showBiometricUnlock: Boolean = true, - biometricUnlockResult: BiometricUnlock.AuthenticationResult? = null, + biometricUnlockResult: BiometricAuthenticator.AuthenticationResult? = null, isUnlocked: Boolean = false, signOutAction: AsyncAction = AsyncAction.Uninitialized, ) = PinUnlockState( diff --git a/features/lockscreen/impl/src/main/res/values/localazy.xml b/features/lockscreen/impl/src/main/res/values/localazy.xml index 0836efa99b..8d6d298b59 100644 --- a/features/lockscreen/impl/src/main/res/values/localazy.xml +++ b/features/lockscreen/impl/src/main/res/values/localazy.xml @@ -3,6 +3,7 @@ "biometric authentication" "biometric unlock" "Unlock with biometric" + "Confirm biometric" "Forgot PIN?" "Change PIN code" "Allow biometric unlock" diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt new file mode 100644 index 0000000000..e6bd169392 --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.lockscreen.impl.biometric + +class FakeBiometricAuthenticator( + override val isActive: Boolean = false, + private val authenticateLambda: suspend () -> BiometricAuthenticator.AuthenticationResult = { BiometricAuthenticator.AuthenticationResult.Success }, +) : BiometricAuthenticator { + override fun setup() = Unit + override suspend fun authenticate() = authenticateLambda() +} diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt new file mode 100644 index 0000000000..2907c98310 --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.lockscreen.impl.biometric + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember + +class FakeBiometricAuthenticatorManager( + override var isDeviceSecured: Boolean = true, + override var hasAvailableAuthenticator: Boolean = false, + private val createBiometricAuthenticator: () -> BiometricAuthenticator = { FakeBiometricAuthenticator() }, +) : BiometricAuthenticatorManager { + override fun addCallback(callback: BiometricAuthenticator.Callback) { + // no-op + } + + override fun removeCallback(callback: BiometricAuthenticator.Callback) { + // no-op + } + + @Composable + override fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator { + return remember { + createBiometricAuthenticator() + } + } + + @Composable + override fun rememberConfirmBiometricAuthenticator(): BiometricAuthenticator { + return remember { + createBiometricAuthenticator() + } + } +} diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt deleted file mode 100644 index 14cbef8a1d..0000000000 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricUnlockManager.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.lockscreen.impl.biometric - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember - -class FakeBiometricUnlockManager : BiometricUnlockManager { - override var isDeviceSecured: Boolean = true - override var hasAvailableAuthenticator: Boolean = false - - override fun addCallback(callback: BiometricUnlock.Callback) { - // no-op - } - - override fun removeCallback(callback: BiometricUnlock.Callback) { - // no-op - } - - @Composable - override fun rememberBiometricUnlock(): BiometricUnlock { - return remember { - NoopBiometricUnlock() - } - } -} diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt index fee45d0b0c..52fdf2e352 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt @@ -7,28 +7,38 @@ package io.element.android.features.lockscreen.impl.settings -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.lockscreen.impl.LockScreenConfig -import io.element.android.features.lockscreen.impl.biometric.FakeBiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.fixtures.aLockScreenConfig import io.element.android.features.lockscreen.impl.fixtures.aPinCodeManager import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore +import io.element.android.features.lockscreen.impl.storage.LockScreenStore import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest import org.junit.Test class LockScreenSettingsPresenterTest { + @Test + fun `present - remove pin option is hidden when mandatory`() = runTest { + val presenter = createLockScreenSettingsPresenter(this, lockScreenConfig = aLockScreenConfig(isPinMandatory = true)) + presenter.test { + awaitItem().also { state -> + assertThat(state.showRemovePinOption).isFalse() + } + } + } + @Test fun `present - remove pin flow`() = runTest { val presenter = createLockScreenSettingsPresenter(this) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { consumeItemsUntilPredicate { state -> state.showRemovePinOption }.last().also { state -> @@ -55,11 +65,95 @@ class LockScreenSettingsPresenterTest { } } + @Test + fun `present - show toggle biometric if device is secured`() = runTest { + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager( + isDeviceSecured = true, + ) + val presenter = createLockScreenSettingsPresenter( + coroutineScope = this, + biometricAuthenticatorManager = fakeBiometricAuthenticatorManager + ) + presenter.test { + skipItems(1) + assertThat(awaitItem().showToggleBiometric).isTrue() + } + } + + @Test + fun `present - enable biometric unlock success`() = runTest { + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager( + createBiometricAuthenticator = { + FakeBiometricAuthenticator(authenticateLambda = { BiometricAuthenticator.AuthenticationResult.Success }) + } + ) + val presenter = createLockScreenSettingsPresenter( + coroutineScope = this, + biometricAuthenticatorManager = fakeBiometricAuthenticatorManager + ) + presenter.test { + skipItems(1) + awaitItem().also { state -> + state.eventSink(LockScreenSettingsEvents.ToggleBiometricAllowed) + } + awaitItem().also { state -> + assertThat(state.isBiometricEnabled).isTrue() + } + } + } + + @Test + fun `present - enable biometric unlock failure`() = runTest { + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager( + createBiometricAuthenticator = { + FakeBiometricAuthenticator(authenticateLambda = { BiometricAuthenticator.AuthenticationResult.Failure() }) + } + ) + val presenter = createLockScreenSettingsPresenter( + coroutineScope = this, + biometricAuthenticatorManager = fakeBiometricAuthenticatorManager + ) + presenter.test { + skipItems(1) + awaitItem().also { state -> + state.eventSink(LockScreenSettingsEvents.ToggleBiometricAllowed) + } + } + } + + @Test + fun `present - disable biometric unlock`() = runTest { + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager( + createBiometricAuthenticator = { + FakeBiometricAuthenticator(authenticateLambda = { BiometricAuthenticator.AuthenticationResult.Failure() }) + } + ) + val lockScreenStore = InMemoryLockScreenStore() + val presenter = createLockScreenSettingsPresenter( + coroutineScope = this, + lockScreenStore = lockScreenStore, + biometricAuthenticatorManager = fakeBiometricAuthenticatorManager + ) + lockScreenStore.setIsBiometricUnlockAllowed(true) + + presenter.test { + skipItems(1) + awaitItem().also { state -> + assertThat(state.isBiometricEnabled).isTrue() + state.eventSink(LockScreenSettingsEvents.ToggleBiometricAllowed) + } + awaitItem().also { state -> + assertThat(state.isBiometricEnabled).isFalse() + } + } + } + private suspend fun createLockScreenSettingsPresenter( coroutineScope: CoroutineScope, lockScreenConfig: LockScreenConfig = aLockScreenConfig(), + biometricAuthenticatorManager: BiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(), + lockScreenStore: LockScreenStore = InMemoryLockScreenStore(), ): LockScreenSettingsPresenter { - val lockScreenStore = InMemoryLockScreenStore() val pinCodeManager = aPinCodeManager(lockScreenStore = lockScreenStore).apply { createPinCode("1234") } @@ -68,7 +162,7 @@ class LockScreenSettingsPresenterTest { pinCodeManager = pinCodeManager, coroutineScope = coroutineScope, lockScreenConfig = lockScreenConfig, - biometricUnlockManager = FakeBiometricUnlockManager(), + biometricAuthenticatorManager = biometricAuthenticatorManager, ) } } diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt index e22bf0b683..83271d4131 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt @@ -11,6 +11,10 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticator +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore import io.element.android.features.lockscreen.impl.storage.LockScreenStore import kotlinx.coroutines.flow.first @@ -19,9 +23,12 @@ import org.junit.Test class SetupBiometricPresenterTest { @Test - fun `present - allow flow`() = runTest { + fun `present - allow flow with biometric authentication success`() = runTest { val lockScreenStore = InMemoryLockScreenStore() - val presenter = createSetupBiometricPresenter(lockScreenStore) + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(createBiometricAuthenticator = { + FakeBiometricAuthenticator(authenticateLambda = { BiometricAuthenticator.AuthenticationResult.Success }) + }) + val presenter = createSetupBiometricPresenter(lockScreenStore, fakeBiometricAuthenticatorManager) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -36,6 +43,24 @@ class SetupBiometricPresenterTest { assertThat(lockScreenStore.isBiometricUnlockAllowed().first()).isTrue() } + @Test + fun `present - allow flow with biometric authentication failure`() = runTest { + val lockScreenStore = InMemoryLockScreenStore() + val fakeBiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(createBiometricAuthenticator = { + FakeBiometricAuthenticator(authenticateLambda = { BiometricAuthenticator.AuthenticationResult.Failure() }) + }) + val presenter = createSetupBiometricPresenter(lockScreenStore, fakeBiometricAuthenticatorManager) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().also { state -> + assertThat(state.isBiometricSetupDone).isFalse() + state.eventSink(SetupBiometricEvents.AllowBiometric) + } + } + assertThat(lockScreenStore.isBiometricUnlockAllowed().first()).isFalse() + } + @Test fun `present - skip flow`() = runTest { val lockScreenStore = InMemoryLockScreenStore() @@ -55,10 +80,12 @@ class SetupBiometricPresenterTest { } private fun createSetupBiometricPresenter( - lockScreenStore: LockScreenStore = InMemoryLockScreenStore() + lockScreenStore: LockScreenStore = InMemoryLockScreenStore(), + biometricAuthenticatorManager: BiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(), ): SetupBiometricPresenter { return SetupBiometricPresenter( lockScreenStore = lockScreenStore, + biometricAuthenticatorManager = biometricAuthenticatorManager ) } } diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt index 98533b9863..cdb0c8143e 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt @@ -11,8 +11,8 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockManager -import io.element.android.features.lockscreen.impl.biometric.FakeBiometricUnlockManager +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticatorManager import io.element.android.features.lockscreen.impl.fixtures.aPinCodeManager import io.element.android.features.lockscreen.impl.pin.DefaultPinCodeManagerCallback import io.element.android.features.lockscreen.impl.pin.PinCodeManager @@ -137,7 +137,7 @@ class PinUnlockPresenterTest { private suspend fun createPinUnlockPresenter( scope: CoroutineScope, - biometricUnlockManager: BiometricUnlockManager = FakeBiometricUnlockManager(), + biometricAuthenticatorManager: BiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(), callback: PinCodeManager.Callback = DefaultPinCodeManagerCallback(), logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" }), ): PinUnlockPresenter { @@ -147,10 +147,10 @@ class PinUnlockPresenterTest { } return PinUnlockPresenter( pinCodeManager = pinCodeManager, - biometricUnlockManager = biometricUnlockManager, + biometricAuthenticatorManager = biometricAuthenticatorManager, logoutUseCase = logoutUseCase, coroutineScope = scope, - pinUnlockHelper = PinUnlockHelper(biometricUnlockManager, pinCodeManager), + pinUnlockHelper = PinUnlockHelper(biometricAuthenticatorManager, pinCodeManager), ) } }