package com.tangem.tap.domain import android.content.res.Resources import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.common.* import com.tangem.common.authentication.KeystoreManager import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.services.secure.SecureStorage import com.tangem.common.usersCode.UserCodeRepository import com.tangem.core.analytics.Analytics import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.derivationsFinder import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlin.coroutines.resume @Suppress("TooManyFunctions") class TangemSdkManager( private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources, ) { private val tangemSdk: TangemSdk get() = cardSdkConfigRepository.sdk private val userCodeRepository by lazy { UserCodeRepository( keystoreManager = tangemSdk.keystoreManager, secureStorage = tangemSdk.secureStorage, ) } val canUseBiometry: Boolean get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics val needEnrollBiometrics: Boolean get() = tangemSdk.authenticationManager.needEnrollBiometrics val keystoreManager: KeystoreManager get() = tangemSdk.keystoreManager val secureStorage: SecureStorage get() = tangemSdk.secureStorage val userCodeRequestPolicy: UserCodeRequestPolicy get() = tangemSdk.config.userCodeRequestPolicy suspend fun scanProduct( cardId: String? = null, messageRes: Int? = null, allowsRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask( card = null, derivationsFinder = derivationsFinder, allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, ), cardId = cardId, initialMessage = message, ).also { sendScanResultsToAnalytics(it) } } suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult { return runTaskAsync( runnable = CreateProductWalletTask( cardTypesResolver = scanResponse.cardTypesResolver, derivationStyleProvider = scanResponse.derivationStyleProvider, ), cardId = scanResponse.card.cardId, initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)), iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null, ) } suspend fun importWallet( scanResponse: ScanResponse, mnemonic: String, ): CompletionResult { val defaultMnemonic = try { DefaultMnemonic(mnemonic, tangemSdk.wordlist) } catch (e: TangemSdkError.MnemonicException) { return CompletionResult.Failure(e) } return runTaskAsync( CreateProductWalletTask( scanResponse.cardTypesResolver, derivationStyleProvider = scanResponse.derivationStyleProvider, defaultMnemonic, ), scanResponse.card.cardId, Message(resources.getString(R.string.initial_message_create_wallet_body)), ) } private fun sendScanResultsToAnalytics(result: CompletionResult) { if (result is CompletionResult.Failure) { (result.error as? TangemSdkError)?.let { error -> Analytics.send(Basic.ScanError(error)) } } } suspend fun derivePublicKeys( cardId: String?, derivations: Map>, ): CompletionResult { return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) } suspend fun resetToFactorySettings( cardId: String, allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult { return runTaskAsyncReturnOnMain( runnable = ResetToFactorySettingsTask( allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, ), cardId = cardId, initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)), ) .map { CardDTO(it) } } suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { return userCodeRepository.save( cardsIds = cardsIds, userCode = UserCode( type = UserCodeType.AccessCode, stringValue = accessCode, ), ) } suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { return userCodeRepository.delete(cardsIds.toSet()) } suspend fun clearSavedUserCodes(): CompletionResult { return userCodeRepository.clear() } suspend fun setPasscode(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeCommand.changePasscode(null), cardId, initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)), ) } suspend fun setAccessCode(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeCommand.changeAccessCode(null), cardId, initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)), ) } suspend fun setLongTap(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeCommand.resetUserCodes(), cardId, initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) } suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeRecoveryAllowedTask(enabled), cardId, initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) } suspend fun scanCard( cardId: String? = null, allowRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { return runTaskAsyncReturnOnMain( runnable = ScanTask(allowRequestAccessCodeFromRepository), cardId = cardId, initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) .map { CardDTO(it) } } suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, accessCode: String? = null, @DrawableRes iconScanRes: Int? = null, ): CompletionResult = withContext(Dispatchers.Main) { suspendCancellableCoroutine { continuation -> tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result -> if (continuation.isActive) continuation.resume(result) } } } private suspend fun runTaskAsyncReturnOnMain( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, ): CompletionResult { val result = runTaskAsync(runnable, cardId, initialMessage) return withContext(Dispatchers.Main) { result } } @Suppress("MagicNumber") fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) { tangemSdk.config.cardIdDisplayFormat = when { scanResponse == null -> CardIdDisplayFormat.Full scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) else -> CardIdDisplayFormat.Full } } @Deprecated("TangemSdkManager shouldn't returns a string from resources") fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { return resources.getString(stringResId, *formatArgs) } fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) { tangemSdk.config.userCodeRequestPolicy = policy } companion object { @Deprecated("Use [DefaultCardSdkProvider] instead") val config = Config( linkedTerminal = true, allowUntrustedCards = true, filter = CardFilter( allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33), ), ) } }