diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt index e3de1dc4a7..0f5e408f5e 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt @@ -33,6 +33,10 @@ interface TangemSdkManager { val userCodeRequestPolicy: UserCodeRequestPolicy + suspend fun checkCanUseBiometry(awaitInitialization: Boolean = true): Boolean + + suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean = true): Boolean + suspend fun scanProduct( cardId: String? = null, messageRes: Int? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 72b578cea9..00bacdbe26 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -3,9 +3,11 @@ package com.tangem.tap.domain.sdk.impl import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import com.tangem.Log import com.tangem.Message import com.tangem.TangemSdk import com.tangem.common.* +import com.tangem.common.authentication.AuthenticationManager import com.tangem.common.authentication.keystore.KeystoreManager import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* @@ -38,16 +40,21 @@ import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask import com.tangem.tap.domain.twins.FinalizeTwinTask import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlin.coroutines.resume -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") class DefaultTangemSdkManager( private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources, ) : TangemSdkManager { + private val awaitInitializationMutex = Mutex() + private val tangemSdk: TangemSdk get() = cardSdkConfigRepository.sdk @@ -73,6 +80,42 @@ class DefaultTangemSdkManager( override val userCodeRequestPolicy: UserCodeRequestPolicy get() = tangemSdk.config.userCodeRequestPolicy + override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean { + return try { + needEnrollBiometrics + } catch (e: TangemSdkError.AuthenticationNotInitialized) { + Log.error { + "Trying to access `needEnrollBiometrics` flag when authentication manager is not initialized: " + + if (awaitInitialization) "awaiting initialization" else "failing" + } + + if (awaitInitialization) { + awaitAuthenticationManagerInitialization().needEnrollBiometrics + } else { + throw e + } + } + } + + override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean { + return try { + canUseBiometry + } catch (e: TangemSdkError.AuthenticationNotInitialized) { + Log.error { + "Trying to access `canUseBiometry` flag when authentication manager is not initialized: " + + if (awaitInitialization) "awaiting initialization" else "failing" + } + + if (awaitInitialization) { + val manager = awaitAuthenticationManagerInitialization() + + manager.canAuthenticate || manager.needEnrollBiometrics + } else { + throw e + } + } + } + override suspend fun scanProduct( cardId: String?, messageRes: Int?, @@ -288,6 +331,26 @@ class DefaultTangemSdkManager( tangemSdk.config.userCodeRequestPolicy = policy } + private suspend fun awaitAuthenticationManagerInitialization(): AuthenticationManager { + return awaitInitializationMutex.withLock { + var attemps = 0 + + do { + if (tangemSdk.authenticationManager.isInitialized) { + break + } else { + if (attemps++ >= MAX_INITIALIZE_ATTEMPTS) { + error("Can't initialize authentication manager after $MAX_INITIALIZE_ATTEMPTS attempts") + } else { + delay(timeMillis = 200) + } + } + } while (true) + + tangemSdk.authenticationManager + } + } + // region Twin-specific override suspend fun createFirstTwinWallet( @@ -335,6 +398,8 @@ class DefaultTangemSdkManager( // endregion companion object { + private const val MAX_INITIALIZE_ATTEMPTS = 10 + @Deprecated("Use [DefaultCardSdkProvider] instead") val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 7c5585c68b..dcc850f798 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -28,9 +28,11 @@ class MockTangemSdkManager( private val resources: Resources, ) : TangemSdkManager { - override val canUseBiometry = false + private var userCodeRequestPolicyInternal: UserCodeRequestPolicy = UserCodeRequestPolicy.Default - override val needEnrollBiometrics = false + override val canUseBiometry: Boolean = false + + override val needEnrollBiometrics: Boolean = false override val keystoreManager = DummyKeystoreManager() @@ -39,7 +41,9 @@ class MockTangemSdkManager( override val userCodeRequestPolicy: UserCodeRequestPolicy get() = userCodeRequestPolicyInternal - private var userCodeRequestPolicyInternal: UserCodeRequestPolicy = UserCodeRequestPolicy.Default + override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean = canUseBiometry + + override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean = needEnrollBiometrics override suspend fun scanProduct( cardId: String?, diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt index 00f7f53887..6a84f8cd30 100644 --- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt @@ -7,5 +7,5 @@ internal class DefaultLegacySettingsRepository( private val tangemSdkManager: TangemSdkManager, ) : LegacySettingsRepository { - override fun canUseBiometry(): Boolean = tangemSdkManager.canUseBiometry + override suspend fun canUseBiometry(): Boolean = tangemSdkManager.checkCanUseBiometry() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index c82a82d087..ba234b662c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -37,7 +37,10 @@ import com.tangem.utils.coroutines.saveIn import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.Action @@ -265,7 +268,6 @@ class DetailsMiddleware { .onEach { needEnrollBiometrics -> store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics)) } - .flowOn(Dispatchers.IO) .launchIn(lifecycleScope) .saveIn(checkBiometricsStatusJobHolder) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 5a915b21d5..68b87d064f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -76,7 +76,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS }, createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appSettingsState = AppSettingsState( - isBiometricsAvailable = tangemSdkManager.canUseBiometry, + isBiometricsAvailable = runBlocking { + tangemSdkManager.checkCanUseBiometry() + }, saveWallets = action.shouldSaveUserWallets, saveAccessCodes = runBlocking { store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index f03dc73425..3aca958d05 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -99,7 +99,7 @@ object OnboardingHelper { } // When should not save user wallets but device has biometry and save wallet screen has not been shown, // then open save wallet screen - tangemSdkManager.canUseBiometry && settingsRepository.shouldShowSaveUserWalletScreen() -> { + tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> { proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) delay(timeMillis = 1_200) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 45eb5225a2..4069551f22 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -91,10 +91,10 @@ internal class SaveWalletMiddleware { store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) } - private fun allowToUseBiometrics(state: SaveWalletState) { - if (tangemSdkManager.needEnrollBiometrics) { - store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) - return + private fun allowToUseBiometrics(state: SaveWalletState) = scope.launch { + if (tangemSdkManager.checkNeedEnrollBiometrics()) { + store.dispatchWithMain(SaveWalletAction.EnrollBiometrics) + return@launch } if (state.backupInfo != null) { @@ -104,24 +104,22 @@ internal class SaveWalletMiddleware { Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) } - scope.launch { - /* + /* - * because it will be automatically saved on UserWalletsListManager switch - */ - val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { - val error = IllegalStateException("No selected user wallet") - Timber.e(error, "Unable to save user wallet") - store.dispatchWithMain( - SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)), - ) - return@launch - } + * because it will be automatically saved on UserWalletsListManager switch + */ + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { + val error = IllegalStateException("No selected user wallet") + Timber.e(error, "Unable to save user wallet") + store.dispatchWithMain( + SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)), + ) + return@launch + } - handleSuccessAllowing(selectedUserWallet) - }.saveIn(saveWalletJobHolder) - } + handleSuccessAllowing(selectedUserWallet) + }.saveIn(saveWalletJobHolder) private suspend fun handleSuccessAllowing(userWallet: UserWallet) { store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true) diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt index 390c70dcc4..880f941db8 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt @@ -4,5 +4,5 @@ import com.tangem.domain.settings.repositories.LegacySettingsRepository class CanUseBiometryUseCase(private val legacySettingsRepository: LegacySettingsRepository) { - operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry() + suspend operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt index 9e1dc7cf3a..e1f1b8ebb7 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt @@ -2,5 +2,5 @@ package com.tangem.domain.settings.repositories interface LegacySettingsRepository { - fun canUseBiometry(): Boolean + suspend fun canUseBiometry(): Boolean } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 41817d438f..b7ef29922c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -89,7 +89,7 @@ markdown = "0.7.2" # region Tangem tangemBlockchainSdk = "release-app_5.12-698" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.12-369" +tangemCardSdk = "release-app_5.12-371" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem