diff --git a/.gitignore b/.gitignore index ae9ed9c524..39d65ab0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ local.properties .idea/caches/ .idea/libraries/ .idea/*.xml +.idea/codeStyles + +# LayoutInspector files +.captures # OS-specific files .DS_Store diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41ae2a..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a179b..0000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/dictionaries/tangem.xml b/.idea/dictionaries/tangem.xml new file mode 100644 index 0000000000..e77bbeb7ed --- /dev/null +++ b/.idea/dictionaries/tangem.xml @@ -0,0 +1,16 @@ + + + + binance + blockchain + cardano + ducatus + ethereum + litecoin + matic + tangem + testnet + tezos + + + \ No newline at end of file diff --git a/app/src/main/java/com/tangem/App.kt b/app/src/main/java/com/tangem/App.kt index b5a4451cb7..ef34464937 100644 --- a/app/src/main/java/com/tangem/App.kt +++ b/app/src/main/java/com/tangem/App.kt @@ -80,8 +80,10 @@ class App : Application() { ) } - FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(Analytics.isEnabled()) FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled()) + FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled( + Analytics.isEnabled() && !BuildConfig.DEBUG + ) } private fun buildToastHelperComponent(): ToastHelperComponent { diff --git a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt index 98b57d7910..0ff8fef384 100644 --- a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt @@ -53,7 +53,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP if (!Analytics.isEnabled()) { analytics.logEvent(AnalyticsEvent.ANALYTICS_TURNED_OFF.event, bundleOf()) } - analytics.setAnalyticsCollectionEnabled(Analytics.isEnabled()) + analytics.setAnalyticsCollectionEnabled(Analytics.isEnabled() && !BuildConfig.DEBUG) } FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled()) @@ -71,10 +71,4 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP preferenceScreen.sharedPreferences .unregisterOnSharedPreferenceChangeListener(this); } - - override fun onDestroyView() { - (activity as AppCompatActivity).setSupportActionBar(toolbar) - super.onDestroyView() - } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/util/Analytics.kt b/app/src/main/java/com/tangem/util/Analytics.kt index c01d40e340..d1120edf6e 100644 --- a/app/src/main/java/com/tangem/util/Analytics.kt +++ b/app/src/main/java/com/tangem/util/Analytics.kt @@ -22,6 +22,7 @@ enum class AnalyticsParam(val param: String) { FIRMWARE("firmware"), ; } + class Analytics { companion object { fun setCardData(ctx: TangemContext): Bundle { diff --git a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt index 0028ee31c5..76d9ce0ffa 100644 --- a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt +++ b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt @@ -4,24 +4,23 @@ import android.os.Bundle import android.text.Editable import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager +import com.tangem.SessionError +import com.tangem.TangemSdk import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.Signer import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain_demo.databinding.ActivityBlockchainDemoBinding import com.tangem.commands.Card +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.coroutines.* import java.math.BigDecimal import kotlin.coroutines.CoroutineContext class BlockchainDemoActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var signer: TransactionSigner private lateinit var card: Card private lateinit var walletManager: WalletManager @@ -47,8 +46,8 @@ class BlockchainDemoActivity : AppCompatActivity() { val view = binding.root setContentView(view) - cardManager = CardManager.init(this) - signer = Signer(cardManager) + tangemSdk = TangemSdk.init(this) + signer = Signer(tangemSdk) binding.btnScan.setOnClickListener { scan() } @@ -59,27 +58,20 @@ class BlockchainDemoActivity : AppCompatActivity() { private fun scan() { - cardManager.scanCard { taskEvent -> - when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - card = (taskEvent.data as ScanEvent.OnReadEvent).card - walletManager = WalletManagerFactory.makeWalletManager(card)!! + tangemSdk.scanCard { result -> + when (result) { + is CompletionResult.Success -> { + walletManager = WalletManagerFactory.makeWalletManager(result.data)!! getInfo() } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error !is TaskError.UserCancelled) { - handleError(taskEvent.error.toString()) + is CompletionResult.Failure -> { + if (result.error !is SessionError.UserCancelled) { + handleError(result.error.toString()) } } } } } - } private fun getInfo() { scope.launch { diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt index 58ae155e27..72a18f08fc 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt @@ -8,8 +8,8 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet +import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskEvent import java.math.BigDecimal class BitcoinWalletManager( @@ -66,11 +66,11 @@ class BitcoinWalletManager( is Result.Failure -> return SimpleResult.Failure(buildTransactionResult.error) is Result.Success -> { when (val signerResponse = signer.sign(buildTransactionResult.data.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) return networkManager.sendTransaction(transactionToSend.toHexString()) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt index edf2acefbb..86b5f25a51 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt @@ -8,7 +8,7 @@ import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.common.extensions.encodeBase64NoWrap import com.tangem.blockchain.wallets.CurrencyWallet -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult class CardanoWalletManager( private val cardId: String, @@ -47,11 +47,11 @@ class CardanoWalletManager( val transactionHash = transactionBuilder.buildToSign(transactionData) when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) return networkManager.sendTransaction(transactionToSend.encodeBase64NoWrap()) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt index c39d9b6c0e..6fe5a862ea 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet import com.tangem.commands.SignResponse -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult interface WalletManager { var wallet: CurrencyWallet @@ -18,7 +18,7 @@ interface TransactionSender { } interface TransactionSigner { - suspend fun sign(hashes: Array, cardId: String): TaskEvent + suspend fun sign(hashes: Array, cardId: String): CompletionResult } interface FeeProvider { diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt index cdf9edd92e..8451a23b67 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt @@ -1,9 +1,9 @@ package com.tangem.blockchain.common.extensions -import com.tangem.CardManager +import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner import com.tangem.commands.SignResponse -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine import java.io.IOException @@ -54,10 +54,10 @@ sealed class SimpleResult { data class Failure(val error: Throwable?) : SimpleResult() } -class Signer(private val cardManager: CardManager) : TransactionSigner { - override suspend fun sign(hashes: Array, cardId: String): TaskEvent = +class Signer(private val tangemSdk: TangemSdk) : TransactionSigner { + override suspend fun sign(hashes: Array, cardId: String): CompletionResult = suspendCancellableCoroutine { continuation -> - cardManager.sign(hashes, cardId) { result -> + tangemSdk.sign(hashes, cardId) { result -> if (continuation.isActive) continuation.resume(result) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt index d7c26e526e..3999bdf7a3 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt @@ -8,8 +8,8 @@ import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.ethereum.network.EthereumNetworkManager import com.tangem.blockchain.ethereum.network.EthereumResponse import com.tangem.blockchain.wallets.CurrencyWallet +import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskEvent import org.kethereum.DEFAULT_GAS_LIMIT import org.kethereum.crypto.api.ec.ECDSASignature import org.kethereum.crypto.determineRecId @@ -81,11 +81,11 @@ class EthereumWalletManager( val transactionToSign = builder.buildToSign(transactionData, txCount.toBigInteger()) ?: return SimpleResult.Failure(Exception("Not enough data")) when (val signerResponse = signer.sign(transactionToSign.hashes.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = builder.buildToSend(signerResponse.data.signature, transactionToSign, walletPublicKey) return networkManager.sendTransaction(String.format("0x%s", transactionToSend.toHexString())) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt index e65278fa35..754cf7fa60 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult import java.math.BigDecimal import java.util.* @@ -58,11 +58,11 @@ class StellarWalletManager( override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val hashes = builder.buildToSign(transactionData, sequence, baseFee.toStroops()) when (val signerResponse = signer.sign(hashes.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = builder.buildToSend(signerResponse.data.signature) return networkManager.sendTransaction(transactionToSend) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt index bba2e26427..cf6c8206a8 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet import com.tangem.blockchain.xrp.network.XrpInfoResponse import com.tangem.blockchain.xrp.network.XrpNetworkManager -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult class XrpWalletManager( private val cardId: String, @@ -68,11 +68,11 @@ class XrpWalletManager( val transactionHash = transactionBuilder.buildToSign(transactionData) when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature) return networkManager.sendTransaction(transactionToSend) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt index a476130557..9ce1fd90ac 100644 --- a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -1,7 +1,7 @@ package com.tangem.blockchain.common import com.google.common.truth.Truth -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment import com.tangem.blockchain.bitcoin.BitcoinWalletManager import com.tangem.blockchain.cardano.CardanoWalletManager import com.tangem.blockchain.ethereum.EthereumWalletManager @@ -18,7 +18,7 @@ internal class WalletManagerFactoryTest { fun createBitcoinWalletManager() { val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -29,7 +29,7 @@ internal class WalletManagerFactoryTest { fun createEthereumWalletManager() { val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -40,7 +40,7 @@ internal class WalletManagerFactoryTest { fun createStellarWalletManager() { val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -51,7 +51,7 @@ internal class WalletManagerFactoryTest { fun createCardanoWalletManager() { val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -62,7 +62,7 @@ internal class WalletManagerFactoryTest { fun createXrpWalletManager() { val data = "0108cb21000000002154200b534d4152542043415348000201028006322e31317200034104bdad63848f97c535da53cf8fd300d24fa33f0516d194aa78ec164a06994d00204bae243a424e316c6ec845e02d9b15eafae8c19018a926b0b7435e6e941cdadb0a0400007e210c5a81020028820407e30502830754414e47454d00840358525086400ed8734b877869722c7d0b37ffb154b9fef21c54bf2c6496feb1fb5c1fc28a2ac28e201dde84f27495fa7f08b3ca2be2fb4954bf0fe78af027d6cdc16c3eee923041048196aa4b410ac44a3b9cce18e7be226aea070acc83a9cf67540fac49af25129f6a538a28ad6341358e3c4f9963064f7e365372a651d374e5c23cdd37fd099bf2050a736563703235366b31000804000f4240070100090205dc604104d2b9fb288540d54e5b32ecaf0381cd571f97f6f1ecd036b66bb11aa52ffe9981110d883080e2e255c6b1640586f7765e6faa325d1340f49b56b83d9de56bc7ed6204000f42406304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) diff --git a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt index 102e7ef8d9..ed9371767b 100644 --- a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt +++ b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt @@ -47,10 +47,11 @@ class LocalStorage artworks = HashMap() } - if (artworks.count() < 43) { + if (artworks.count() < 44) { // forceSave=true only on the last one putResourceArtworkToCatalog(R.drawable.card_default, false) putResourceArtworkToCatalog(R.drawable.card_default_nft, false) + putResourceArtworkToCatalog(R.drawable.card_id_issuer, false) putResourceArtworkToCatalog(R.drawable.card_ru006, false) putResourceArtworkToCatalog(R.drawable.card_ru007, false) putResourceArtworkToCatalog(R.drawable.card_ru011, false) @@ -229,7 +230,7 @@ class LocalStorage fun getCardArtworkBitmap(card: TangemCard): Bitmap { // special cases (first series of cards, hardcode CID->artwork), on new series batch<->artwork - if (card.isIDIssuer) { + if (card.isIDIssuer || isIdSergio(card)) { val artworkId = context.resources.getResourceEntryName(R.drawable.card_id_issuer) return getArtworkBitmap(artworkId) ?: return getDefaultArtworkBitmap(card) } @@ -374,4 +375,10 @@ class LocalStorage } } +} + +fun LocalStorage.isIdSergio(card: TangemCard): Boolean { + val key = card.walletPublicKey ?: return false + val hexString = Util.bytesToHex(key) + return hexString == "04EAD74FEEE4061044F46B19EB654CEEE981E9318F0C8FE99AF5CDB9D779D2E52BB51EA2D14545E0B323F7A90CF4CC72753C973149009C10DB2D83DCEC28487729" } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt deleted file mode 100644 index c5a1f99e77..0000000000 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.tangem - -import com.tangem.commands.* -import com.tangem.commands.personalization.DepersonalizeCommand -import com.tangem.commands.personalization.DepersonalizeResponse -import com.tangem.commands.personalization.PersonalizeCommand -import com.tangem.commands.personalization.entities.Acquirer -import com.tangem.commands.personalization.entities.CardConfig -import com.tangem.commands.personalization.entities.Issuer -import com.tangem.commands.personalization.entities.Manufacturer -import com.tangem.common.CardEnvironment -import com.tangem.common.TerminalKeysService -import com.tangem.crypto.CryptoUtils -import com.tangem.tasks.* - -/** - * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. - * - * @property reader is an interface that is responsible for NFC connection and - * transfer of data to and from the Tangem Card. - * Its default implementation, NfcCardReader, is in our tangem-sdk module. - * @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI. - * Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module. - */ -class CardManager( - private val reader: CardReader, - private val cardManagerDelegate: CardManagerDelegate? = null, - private val config: Config = Config() -) { - - private var terminalKeysService: TerminalKeysService? = null - private var isBusy = false - - init { - CryptoUtils.initCrypto() - } - - /** - * To start using any card, you first need to read it using the scanCard() method. - * This method launches an NFC session, and once it’s connected with the card, - * it obtains the card data. Optionally, if the card contains a wallet (private and public key pair), - * it proves that the wallet owns a private key that corresponds to a public one. - * - * It launches on the new thread a [ScanTask] that will send the following events in a callback: - * [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand] - * [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand] - * [TaskEvent.Completion] with an error field null after successful completion of a task or - * [TaskEvent.Completion] with a [TaskError] if some error occurs. - * @param callback is triggered on events during a performance of the task, - * provides data in form of [ScanEvent] subclasses. - */ - fun scanCard(callback: (result: TaskEvent) -> Unit) { - val task = ScanTask() - runTask(task, callback = callback) - } - - /** - * This method allows you to sign one or multiple hashes. - * Simultaneous signing of array of hashes in a single [SignCommand] is required to support - * Bitcoin-type multi-input blockchains (UTXO). - * The [SignCommand] will return a corresponding array of signatures. - * - * This method launches on the new thread [SignCommand] that will send the following events in a callback: - * [SignResponse] after completing [SignCommand] - * [TaskEvent.Completion] with an error field null after successful completion of a task or - * [TaskEvent.Completion] with a [TaskError] if some error occurs. - * Please note that Tangem cards usually protect the signing with a security delay - * that may last up to 90 seconds, depending on a card. - * It is for [CardManagerDelegate] to notify users of security delay. - * @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length. - * @param cardId CID, Unique Tangem card ID number - * @param callback is triggered on the completion of the [SignCommand], - * provides card response in the form of [SignResponse]. - */ - fun sign(hashes: Array, cardId: String, - callback: (result: TaskEvent) -> Unit) { - val signCommand = SignCommand(hashes) - val task = SingleCommandTask(signCommand) - runTask(task, cardId, callback) - } - - /** - * This command returns 512-byte Issuer Data field and its issuer’s signature. - * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, - * format and payload of Issuer Data. For example, this field may contain information about - * wallet balance signed by the issuer or additional issuer’s attestation data. - * @param cardId CID, Unique Tangem card ID number. - * @param callback is triggered on the completion of the [ReadIssuerDataCommand], - * provides card response in the form of [ReadIssuerDataResponse]. - */ - fun readIssuerData(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val task = ReadIssuerDataTask(config.issuerPublicKey) - runTask(task, cardId, callback) - } - - /** - * This task retrieves Issuer Extra Data field and its issuer’s signature. - * Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, - * format and payload of Issuer Data. . For example, this field may contain photo or - * biometric information for ID card product. Because of the large size of Issuer_Extra_Data, - * a series of these commands have to be executed to read the entire Issuer_Extra_Data. - * @param cardId CID, Unique Tangem card ID number. - * @param callback is triggered on the completion of the [ReadIssuerExtraDataTask], - * provides card response in the form of [ReadIssuerExtraDataResponse]. - */ - fun readIssuerExtraData(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val task = ReadIssuerExtraDataTask(config.issuerPublicKey) - runTask(task, cardId, callback) - } - - /** - * This command writes 512-byte Issuer Data field and its issuer’s signature. - * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, - * format and payload of Issuer Data. For example, this field may contain information about - * wallet balance signed by the issuer or additional issuer’s attestation data. - * @param cardId CID, Unique Tangem card ID number. - * @param issuerData Data provided by issuer. - * @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key. - * @param issuerDataCounter An optional counter that protect issuer data against replay attack. - * @param callback is triggered on the completion of the [WriteIssuerDataCommand], - * provides card response in the form of [WriteIssuerDataResponse]. - */ - fun writeIssuerData(cardId: String, - issuerData: ByteArray, - issuerDataSignature: ByteArray, - issuerDataCounter: Int? = null, - callback: (result: TaskEvent) -> Unit) { - val task = WriteIssuerDataTask( - issuerData, - issuerDataSignature, - issuerDataCounter, - config.issuerPublicKey - ) - runTask(task, cardId, callback) - } - - /** - * This task writes Issuer Extra Data field and its issuer’s signature. - * Issuer Extra Data is never changed or parsed from within the Tangem COS. - * The issuer defines purpose of use, format and payload of Issuer Data. - * For example, this field may contain a photo or biometric information for ID card products. - * Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed - * to write entire Issuer_Extra_Data. - * @param cardId CID, Unique Tangem card ID number. - * @param issuerData Data provided by issuer. - * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], - * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and - * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. - * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], - * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay - * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). - * @param issuerDataCounter An optional counter that protect issuer data against replay attack. - * @param callback is triggered on the completion of the [WriteIssuerDataCommand], - * provides card response in the form of [WriteIssuerDataResponse]. - */ - fun writeIssuerExtraData(cardId: String, - issuerData: ByteArray, - startingSignature: ByteArray, - finalizingSignature: ByteArray, - issuerDataCounter: Int? = null, - callback: (result: TaskEvent) -> Unit) { - val task = WriteIssuerExtraDataTask( - issuerData, - startingSignature, finalizingSignature, - config.issuerPublicKey, - issuerDataCounter - ) - runTask(task, cardId, callback) - } - - /** - * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - * - * Writing of User_Counter and User_Data protected only by PIN1. - * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. - */ - fun writeUserData( - cardId: String, - userData: ByteArray? = null, - userProtectedData: ByteArray? = null, - userCounter: Int? = null, - userProtectedCounter: Int? = null, - callback: (result: TaskEvent) -> Unit - ) { - val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) - val task = SingleCommandTask(writeUserDataCommand) - runTask(task, cardId, callback) - } - - /** - * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and - * User_Protected_Counter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - */ - fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) - } - - /** - * This command will create a new wallet on the card having ‘Empty’ state. - * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card. - * App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand] - * and then transform it into an address of corresponding blockchain wallet - * according to a specific blockchain algorithm. - * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand]. - * RemainingSignature is set to MaxSignatures. - * @param cardId CID, Unique Tangem card ID number. - */ - fun createWallet(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val createWalletCommand = CreateWalletCommand() - val task = SingleCommandTask(createWalletCommand) - runTask(task, cardId, callback) - } - - /** - * This command deletes all wallet data. If Is_Reusable flag is enabled during personalization, - - * If Is_Reusable flag is disabled, the card switches to ‘Purged’ state. - * ‘Purged’ state is final, it makes the card useless. - * @param cardId CID, Unique Tangem card ID number. - */ - fun purgeWallet(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val purgeWalletCommand = PurgeWalletCommand() - val task = SingleCommandTask(purgeWalletCommand) - runTask(task, cardId, callback) - } - - /** - * Command available on SDK cards only - * - * This command resets card to initial state, - * erasing all data written during personalization and usage. - * @param cardId CID, Unique Tangem card ID number. - */ - fun depersonalize(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val depersonalizeCommand = DepersonalizeCommand() - val task = SingleCommandTask(depersonalizeCommand) - runTask(task, cardId, callback) - } - - /** - * Command available on SDK cards only - * - * Personalization is an initialization procedure, required before starting using a card. - * During this procedure a card setting is set up. - * During this procedure all data exchange is encrypted. - * @param config is a configuration file with all the card settings that are written on the card - * during personalization. - * @param issuer Issuer is a third-party team or company wishing to use Tangem cards. - * @param manufacturer Tangem Card Manufacturer. - * @param acquirer Acquirer is a trusted third-party company that operates proprietary - * (non-EMV) POS terminal infrastructure and transaction processing back-end. - */ - fun personalize(config: CardConfig, - issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, - callback: (result: TaskEvent) -> Unit) { - val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer) - val task = SingleCommandTask(personalizationCommand) - task.performPreflightRead = false - runTask(task, callback = callback) - } - - /** - - */ - fun runTask(task: Task, cardId: String? = null, callback: (result: TaskEvent) -> Unit) { - if (isBusy) { - callback(TaskEvent.Completion(TaskError.Busy())) - return - } - - val environment = prepareCardEnvironment(cardId) - isBusy = true - - task.reader = reader - task.delegate = cardManagerDelegate - - Thread().run { - task.run(environment) { taskEvent -> - if (taskEvent is TaskEvent.Completion) isBusy = false - callback(taskEvent) - } - } - } - - /** - - */ - fun runCommand(command: CommandSerializer, - cardId: String? = null, - callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(command) - runTask(task, cardId, callback) - } - - /** - * Allows to set a particular [TerminalKeysService] to retrieve terminal keys. - * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. - */ - fun setTerminalKeysService(terminalKeysService: TerminalKeysService) { - this.terminalKeysService = terminalKeysService - } - - private fun prepareCardEnvironment(cardId: String?): CardEnvironment { - val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null - return CardEnvironment( - cardId = cardId, - terminalKeys = terminalKeys - ) - } - - companion object -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt new file mode 100644 index 0000000000..9c338ca586 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -0,0 +1,226 @@ +package com.tangem + +import com.tangem.commands.Card +import com.tangem.commands.CommandResponse +import com.tangem.commands.OpenSessionCommand +import com.tangem.commands.ReadCommand +import com.tangem.common.CompletionResult +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash + +/** + * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] + */ +interface CardSessionRunnable { + + /** + * The starting point for custom business logic. + * Implement this interface and use [TangemSdk.startSession] to run. + * @param session run commands in this [CardSession]. + * @param callback trigger the callback to complete the task. + */ + fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) +} + +/** + * Allows interaction with Tangem cards. Should be opened before sending commands. + * + * @property environment + * @property reader is an interface that is responsible for NFC connection and + * transfer of data to and from the Tangem Card. + * @property viewDelegate is an interface that allows interaction with users and shows relevant UI. + * @property cardId ID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @property initialMessage A custom description that will be shown at the beginning of the NFC session. + * If null, a default header and text body will be used. + */ +class CardSession( + val environment: SessionEnvironment, + private val reader: CardReader, + val viewDelegate: SessionViewDelegate, + private var cardId: String? = null, + private val initialMessage: Message? = null +) { + + private val tag = this.javaClass.simpleName + /** + * True if some operation is still in progress. + */ + private var isBusy = false + + /** + * This metod starts a card session, performs preflight [ReadCommand], + * invokes [CardSessionRunnable.run] and closes the session. + * @param runnable [CardSessionRunnable] that will be performed in the session. + * @param callback will be triggered with a [CompletionResult] of a session. + */ + fun , R : CommandResponse> startWithRunnable( + runnable: T, callback: (result: CompletionResult) -> Unit) { + + start { session, error -> + if (error != null) { + callback(CompletionResult.Failure(error)) + return@start + } + if (runnable is ReadCommand) { + callback(CompletionResult.Success(environment.card as R)) + return@start + } + + runnable.run(this) { result -> + stop() + callback(result) + } + } + } + + /** + * Starts a card session and performs preflight [ReadCommand]. + * @param callback: callback with the card session. Can contain [SessionError] if something goes wrong. + */ + fun start(callback: (session: CardSession, error: SessionError?) -> Unit) { + try { + startSession() + } catch (error: SessionError) { + callback(this, error) + } + + preflightRead() { result -> + when (result) { + is CompletionResult.Failure -> { + callback(this, result.error) + stopWithError(result.error) + } + is CompletionResult.Success -> { + callback(this, null) + + } + } + } + } + + private fun startSession() { + if (isBusy) throw SessionError.Busy() + isBusy = true + viewDelegate.onNfcSessionStarted(cardId, initialMessage) + reader.openSession() + } + + private fun preflightRead(callback: (result: CompletionResult) -> Unit) { + val readCommand = ReadCommand() + readCommand.run(this) { result -> + when (result) { + is CompletionResult.Failure -> { + tryHandleError(result.error) { handleErrorResult -> + when (handleErrorResult) { + is CompletionResult.Success -> preflightRead(callback) + is CompletionResult.Failure -> { + stopWithError(result.error) + callback(CompletionResult.Failure(result.error)) + } + } + } + } + is CompletionResult.Success -> { + val receivedCardId = result.data.cardId + if (cardId != null && receivedCardId != cardId) { + stopWithError(SessionError.WrongCard()) + callback(CompletionResult.Failure(SessionError.WrongCard())) + return@run + } + environment.card = result.data + cardId = receivedCardId + callback(CompletionResult.Success(result.data)) + } + } + } + } + + /** + * Stops the current session with the text message. + * @param message If null, the default message will be shown. + */ + private fun stop(message: Message? = null) { + reader.closeSession() + viewDelegate.onNfcSessionCompleted(message) + isBusy = false + } + + /** + * Stops the current session on error. + * @param error An error that will be shown. + */ + private fun stopWithError(error: Exception) { + reader.closeSession() + isBusy = false + + val errorMessage = if (error is SessionError) { + "${error::class.simpleName}: ${error.code}" + } else { + error.localizedMessage + } + if (error !is SessionError.UserCancelled) { + Log.e("tag", "Finishing with error: $errorMessage") + viewDelegate.onError(errorMessage) + } + } + + fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { + reader.transceiveApdu(apdu, callback) + } + + private fun tryHandleError( + error: SessionError, callback: (result: CompletionResult) -> Unit) { + + when (error) { + is SessionError.NeedEncryption -> { + Log.i(tag, "Establishing encryption") + when (environment.encryptionMode) { + EncryptionMode.NONE -> { + environment.encryptionKey = null + environment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + environment.encryptionKey = null + environment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(tag, "Encryption doesn't work") + callback(CompletionResult.Failure(SessionError.NeedEncryption())) + } + } + return establishEncryption(callback) + } + else -> callback(CompletionResult.Failure(SessionError.UnknownError())) + } + } + + private fun establishEncryption(callback: (result: CompletionResult) -> Unit) { + val encryptionHelper: EncryptionHelper = + if (environment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) + openSesssionCommand.run(this) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + environment.encryptionKey = sessionKey + callback(CompletionResult.Success(true)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 2d1193e176..d99c57fcf9 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -1,6 +1,33 @@ package com.tangem class Config( + /** + * Enables or disables Linked Terminal feature. + + App can optionally generate ECDSA key pair Terminal_PrivateKey / Terminal_PublicKey. + And then submit Terminal_PublicKey to the card in any SIGN command. + Once SIGN is successfully executed by COS (Card Operation System), + including PIN2 verification and/or completion of security delay, the submitted + Terminal_PublicKey key is stored by COS. After that, the App instance is deemed trusted + by COS and COS will allow skipping security delay for subsequent SIGN operations + thus improving convenience without sacrificing security. + + In order to skip security delay, App should use Terminal_PrivateKey to compute the signature + of the data being submitted to SIGN command for signing and transmit this signature in + Terminal_Transaction_Signature parameter in the same SIGN command. COS will verify + the correctness of Terminal_Transaction_Signature using previously stored Terminal_PublicKey + and, if correct, will skip security delay for the current SIGN operation. + */ val linkedTerminal: Boolean = true, - val issuerPublicKey: ByteArray? = null + + /** + * If not null, it will be used to validate Issuer data and issuer extra data. + * If null, issuerPublicKey from current card will be used. + */ + val issuerPublicKey: ByteArray? = null, + + /** + * Level of encryption used in communication with a Tangem Card. + */ + val encryptionMode: EncryptionMode = EncryptionMode.NONE ) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt similarity index 53% rename from tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt rename to tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 09cfd736b4..0dabbc74d6 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -1,29 +1,45 @@ -package com.tangem.common +package com.tangem +import com.tangem.commands.Card import com.tangem.commands.EllipticCurve +import com.tangem.common.extensions.calculateSha256 import com.tangem.crypto.CryptoUtils.generatePublicKey /** * Contains data relating to a Tangem card. It is used in constructing all the commands, - * and commands can return modified [CardEnvironment]. + * and commands can return modified [SessionEnvironment]. + * + * @property card Current card, read by preflight [com.tangem.commands.ReadCommand]. + * @property terminalKeys generated terminal keys used in Linked Terminal feature. */ -data class CardEnvironment( - val pin1: String = DEFAULT_PIN, - val pin2: String = DEFAULT_PIN2, - val cardId: String? = null, +data class SessionEnvironment( + var pin1: ByteArray = DEFAULT_PIN.calculateSha256(), + var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(), + var card: Card? = null, val terminalKeys: KeyPair? = null, var encryptionMode: EncryptionMode = EncryptionMode.NONE, var encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { + fun setPin1(pin1: String) { + this.pin1 = pin1.calculateSha256() + } + + fun setPin2(pin2: String) { + this.pin2 = pin2.calculateSha256() + } + companion object { const val DEFAULT_PIN = "000000" const val DEFAULT_PIN2 = "000" } } +/** + * All possible encryption modes. + */ enum class EncryptionMode(val code: Byte) { NONE(0x0), FAST(0x1), diff --git a/tangem-core/src/main/java/com/tangem/SessionError.kt b/tangem-core/src/main/java/com/tangem/SessionError.kt new file mode 100644 index 0000000000..07209b6a3f --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/SessionError.kt @@ -0,0 +1,139 @@ +package com.tangem + +import com.tangem.commands.Card +import com.tangem.commands.ReadCommand +import com.tangem.common.apdu.StatusWord +import com.tangem.tasks.ScanTask + +/** + * An error class that represent typical errors that may occur when performing Tangem SDK tasks. + * Errors are propagated back to the caller in callbacks. + */ +sealed class SessionError(val code: Int) : Exception() { + + //Errors in serializing APDU + /** + * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] + * (this error is a wrapper around internal [com.tangem.common.tlv.TlvDecoder] errors). + */ + class SerializeCommandError : SessionError(1000) + + class DeserializeApduFailed : SessionError(1001) + class EncodingFailedTypeMismatch : SessionError(1002) + class EncodingFailed : SessionError(1003) + + class DecodingFailedMissingTag : SessionError(1004) + class DecodingFailedTypeMismatch : SessionError(1005) + class DecodingFailed : SessionError(1005) + + /** + * This error is returned when unknown [StatusWord] is received from a card. + */ + class UnknownStatus : SessionError(2001) + + /** + * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. + * The card sends this status in case of internal card error. + */ + class ErrorProcessingCommand : SessionError(2002) + + /** + * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] + * is executed before performing other commands. + */ + class MissingPreflightRead : SessionError(2003) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidState]. + * The card sends this status when command can not be executed in the current state of a card. + */ + class InvalidState : SessionError(2004) + + /** + * This error is returned when a card's reply is [StatusWord.InsNotSupported]. + * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. + */ + class InsNotSupported : SessionError(2005) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidParams]. + * The card sends this status when there are wrong or not sufficient parameters in TLV request, + * or wrong PIN1/PIN2. + * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], + * mapping or serialization errors. + */ + class InvalidParams : SessionError(2006) + + /** + * This error is returned when a card's reply is [StatusWord.NeedEncryption] + * and the encryption was not established by TangemSdk. + */ + class NeedEncryption : SessionError(2007) + + //Scan errors + /** + * This error is returned when a [Task] checks unsuccessfully either + * a card's ability to sign with its private key, or the validity of issuer data. + */ + class VerificationFailed : SessionError(3000) + + /** + * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. + */ + class CardError : SessionError(3001) + + /** + * This error is returned when a [Task] expects a user to use a particular card, + * and a user tries to use a different card. + */ + class WrongCard : SessionError(3002) + + /** + * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. + * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. + */ + class TooMuchHashesInOneTransaction : SessionError(3003) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives only empty hashes for signature. + */ + class EmptyHashes : SessionError(3004) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives hashes of different lengths for signature. + */ + class HashSizeMustBeEqual : SessionError(3005) + + /** + * This error is returned when [com.tangem.TangemSdk] was called with a new [Task], + * while a previous [Task] is still in progress. + */ + class Busy : SessionError(4000) + + /** + * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. + */ + class UserCancelled : SessionError(4001) + + //NFC errors + class NfcReaderError : SessionError(5002) + + /** + * This error is returned when Android NFC reader loses a tag + * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. + */ + class TagLost : SessionError(5003) + + class UnknownError : SessionError(6000) + + //Specific Command Errors + /** + * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter + * (when the card's requires it), but the counter is missing. + */ + class MissingCounter : SessionError(7001) + + class MissingIssuerPubicKey : SessionError(7002) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt similarity index 78% rename from tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt rename to tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 5fce77186d..c372b1730c 100644 --- a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,19 +1,18 @@ package com.tangem import com.tangem.common.CompletionResult -import com.tangem.tasks.TaskError /** * Allows interaction with users and shows visual elements. * * Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module. */ -interface CardManagerDelegate { +interface SessionViewDelegate { /** * It is called when user is expected to scan a Tangem Card with an Android device. */ - fun onNfcSessionStarted(cardId: String?) + fun onNfcSessionStarted(cardId: String?, message: Message? = null) /** * It is called when security delay is triggered by the card. @@ -36,16 +35,21 @@ interface CardManagerDelegate { /** * It is called when NFC session was completed and a user can take the card away from the Android device. */ - fun onNfcSessionCompleted() + fun onNfcSessionCompleted(message: Message? = null) /** * It is called when some error occur during NFC session. */ - fun onError(error: TaskError) + fun onError(errorMessage: String) /** * It is called when a user is expected to enter pin code. */ fun onPinRequested(callback: (result: CompletionResult) -> Unit) -} \ No newline at end of file +} + +/** + * Wrapper for a message that can be shown to user after a start of NFC session. + */ +data class Message(val header: String? = null, val body: String? = null) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt new file mode 100644 index 0000000000..145efc088e --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -0,0 +1,375 @@ +package com.tangem + +import com.tangem.commands.* +import com.tangem.commands.personalization.DepersonalizeCommand +import com.tangem.commands.personalization.DepersonalizeResponse +import com.tangem.commands.personalization.PersonalizeCommand +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer +import com.tangem.common.CompletionResult +import com.tangem.common.TerminalKeysService +import com.tangem.crypto.CryptoUtils +import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.ScanTask + +/** + * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. + * + * @property reader is an interface that is responsible for NFC connection and + * transfer of data to and from the Tangem Card. + * Its default implementation, NfcCardReader, is in our tangem-sdk module. + * @property viewDelegate An interface that allows interaction with users and shows relevant UI. + * Its default implementation, DefaultCardSessionViewDelegate, is in our tangem-sdk module. + * @property config allows to change a number of parameters for communication with Tangem cards. + * Do not change the default values unless you know what you are doing. + */ +class TangemSdk( + private val reader: CardReader, + private val viewDelegate: SessionViewDelegate, + var config: Config = Config() +) { + + private var terminalKeysService: TerminalKeysService? = null + + init { + CryptoUtils.initCrypto() + } + + /** + * This method launches a [ScanTask] on a new thread. + * + * To start using any card, you first need to read it using the scanCard() method. + * This method launches an NFC session, and once it’s connected with the card, + * it obtains the card data. Optionally, if the card contains a wallet (private and public key pair), + * it proves that the wallet owns a private key that corresponds to a public one. + * + * @param callback is triggered on the completion of the [ScanTask] and provides card response + * in the form of [Card] if the task was performed successfully or [SessionError] in case of an error. + */ + fun scanCard(initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { + startSession(ScanTask(), null, initialMessage, callback) + } + + /** + * This method launches a [SignCommand] on a new thread. + * + * It allows you to sign one or multiple hashes. + * Simultaneous signing of array of hashes in a single [SignCommand] is required to support + * Bitcoin-type multi-input blockchains (UTXO). + * The [SignCommand] will return a corresponding array of signatures. + * + * Please note that Tangem cards usually protect the signing with a security delay + * that may last up to 90 seconds, depending on a card. + * It is for [SessionViewDelegate] to notify users of security delay. + * + * @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length. + * @param cardId CID, Unique Tangem card ID number + * @param callback is triggered on the completion of the [SignCommand] and provides card response + * in the form of [SignResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun sign(hashes: Array, cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(SignCommand(hashes), cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadIssuerDataCommand] on a new thread. + + * This command returns 512-byte Issuer Data field and its issuer’s signature. + * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. For example, this field may contain information about + * wallet balance signed by the issuer or additional issuer’s attestation data. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [ReadIssuerDataCommand] and provides + * card response in the form of [ReadIssuerDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun readIssuerData(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadIssuerDataCommand(config.issuerPublicKey), cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadIssuerExtraDataCommand] on a new thread. + * + * This command retrieves Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. . For example, this field may contain photo or + * biometric information for ID card product. Because of the large size of Issuer_Extra_Data, + * a series of these commands have to be executed to read the entire Issuer_Extra_Data. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [ReadIssuerExtraDataCommand] and provides + * card response in the form of [ReadIssuerExtraDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun readIssuerExtraData(cardId: String, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadIssuerExtraDataCommand(config.issuerPublicKey), cardId, null, callback) + } + + /** + * This method launches a [WriteIssuerDataCommand] on a new thread. + * + * This command writes 512-byte Issuer Data field and its issuer’s signature. + * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. For example, this field may contain information about + * wallet balance signed by the issuer or additional issuer’s attestation data. + * + * @param cardId CID, Unique Tangem card ID number. + * @param issuerData Data provided by issuer. + * @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key. + * @param issuerDataCounter An optional counter that protect issuer data against replay attack. + * @param callback is triggered on the completion of the [WriteIssuerDataCommand] and provides + * card response in the form of [WriteIssuerDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun writeIssuerData(cardId: String, + issuerData: ByteArray, + issuerDataSignature: ByteArray, + issuerDataCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = WriteIssuerDataCommand( + issuerData, + issuerDataSignature, + issuerDataCounter, + config.issuerPublicKey + ) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [WriteIssuerExtraDataCommand] on a new thread. + * + * This command writes Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. + * The issuer defines purpose of use, format and payload of Issuer Data. + * For example, this field may contain a photo or biometric information for ID card products. + * Because of the large size of IssuerExtraData, a series of these commands have to be executed + * to write entire IssuerExtraData. + * + * @param cardId CID, Unique Tangem card ID number. + * @param issuerData Data provided by issuer. + * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and + * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. + * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay + * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). + * @param issuerDataCounter An optional counter that protect issuer data against replay attack. + * @param callback is triggered on the completion of the [WriteIssuerExtraDataCommand] and provides + * card response in the form of [WriteIssuerDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun writeIssuerExtraData(cardId: String, + issuerData: ByteArray, + startingSignature: ByteArray, + finalizingSignature: ByteArray, + issuerDataCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = WriteIssuerExtraDataCommand( + issuerData, + startingSignature, finalizingSignature, + issuerDataCounter, + config.issuerPublicKey + ) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [WriteUserDataCommand] on a new thread. + * + * This command writes some of UserData, UserProtectedData, UserCounter and UserProtectedCounter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * Writing of UserCounter and UserData is protected only by PIN1. + * UserProtectedCounter and UserProtectedData need additionally PIN2 to confirmation. + */ + fun writeUserData( + cardId: String, + userData: ByteArray? = null, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit + ) { + val command = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadUserDataCommand] on a new thread. + * + * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and + * User_Protected_Counter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [ReadUserDataCommand] and provides + * card response in the form of [ReadUserDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun readUserData(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadUserDataCommand(), cardId, initialMessage, callback) + } + + /** + * This method launches a [CreateWalletTask] on a new thread. + * + * This this will create a new wallet on the card having ‘Empty’ state with [CreateWalletCommand] + * and will check the success of the operation by performing [CheckWalletCommand]. + * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card. + * App will need to obtain Wallet_PublicKey from the [CreateWalletResponse] or from the + * response of [ReadCommand] and then transform it into an address of corresponding + * blockchain wallet according to a specific blockchain algorithm. + * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand]. + * RemainingSignature is set to MaxSignatures. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [CreateWalletTask] and provides + * card response in the form of [CreateWalletResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun createWallet(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(CreateWalletTask(), cardId, initialMessage, callback) + } + + /** + * This method launches a [PurgeWalletCommand] on a new thread. + * + * This command deletes all wallet data. If IsReusable flag is enabled during personalization, + + * or [CreateWalletCommand]. + * If IsReusable flag is disabled, the card switches to ‘Purged’ state. + * ‘Purged’ state is final, it makes the card useless. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [PurgeWalletCommand] and provides + * card response in the form of [PurgeWalletResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun purgeWallet(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(PurgeWalletCommand(), cardId, initialMessage, callback) + } + + /** + * Command available on SDK cards only + * + * This method launches a [DepersonalizeCommand] on a new thread. + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [DepersonalizeCommand] and provides + * card response in the form of [DepersonalizeResponse] if the task was performed successfully + * or [SessionError] in case of an error. + * */ + fun depersonalize(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(DepersonalizeCommand(), cardId, initialMessage, callback) + } + + /** + * Command available on SDK cards only + * + * This method launches a [PersonalizeCommand] on a new thread. + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param issuer Issuer is a third-party team or company wishing to use Tangem cards. + * @param manufacturer Tangem Card Manufacturer. + * @param acquirer Acquirer is a trusted third-party company that operates proprietary + * (non-EMV) POS terminal infrastructure and transaction processing back-end. + * @param callback is triggered on the completion of the [PersonalizeCommand] and provides + * card response in the form of [Card] if the command was performed successfully + * or [SessionError] in case of an error. + */ + fun personalize(config: CardConfig, + issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = PersonalizeCommand(config, issuer, manufacturer, acquirer) + startSession(command, null, initialMessage, callback) + } + + /** + * Allows running a custom bunch of commands in one [CardSession] by creating a custom task. + * [TangemSdk] will start a card session, perform preflight [ReadCommand], + * invoke [CardSessionRunnable.run] and close the session. + * You can find the current card in the [CardSession.environment]. + + * @runnable: A custom task, adopting [CardSessionRunnable] protocol + * @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @initialMessage: A custom description that shows at the beginning of the NFC session. + * If null, default message will be used. + * @callback: Standard [TangemSdk] callback. + */ + fun startSession( + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + Thread().run { cardSession.startWithRunnable(runnable, callback) } + } + + /** + * Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax. + * Tangem SDK will start a card sesion and perform preflight [ReadCommand]. + + * @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @initialMessage: A custom description that shows at the beginning of the NFC session. + * If null, default message will be used. + * @callback: At first, you should check that the [SessionError] is not null, + * then you can use the [CardSession] to interact with a card. + */ + fun startSession( + cardId: String? = null, initialMessage: Message? = null, + callback: (session: CardSession, error: SessionError?) -> Unit) { + val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + Thread().run { cardSession.start(callback) } + } + + /** + * Allows to set a particular [TerminalKeysService] to retrieve terminal keys. + * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. + */ + fun setTerminalKeysService(terminalKeysService: TerminalKeysService) { + this.terminalKeysService = terminalKeysService + } + + private fun buildEnvironment(): SessionEnvironment { + val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + return SessionEnvironment( + terminalKeys = terminalKeys + ) + } + + companion object +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index 28f0cd2d43..4d9c3a53ff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -1,14 +1,16 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.CryptoUtils -import com.tangem.tasks.TaskError /** * Deserialized response from the Tangem card after [CheckWalletCommand]. @@ -40,33 +42,54 @@ class CheckWalletResponse( * @property cardId Unique Tangem card ID number * @property challenge Random challenge generated by application */ -class CheckWalletCommand : CommandSerializer() { +class CheckWalletCommand( + private val curve: EllipticCurve, private val publicKey: ByteArray +) : Command() { - val challenge = CryptoUtils.generateRandomBytes(16) + private val challenge = CryptoUtils.generateRandomBytes(16) - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + transceive(session) { result -> + when (result) { + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + is CompletionResult.Success -> { + val verified = result.data.verify( + curve, + publicKey, + challenge + ) + if (verified) { + callback(CompletionResult.Success(result.data)) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + } + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) return CommandApdu( Instruction.CheckWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - CheckWalletResponse( - cardId = mapper.map(TlvTag.CardId), - salt = mapper.map(TlvTag.Salt), - walletSignature = mapper.map(TlvTag.Signature) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return CheckWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + salt = decoder.decode(TlvTag.Salt), + walletSignature = decoder.decode(TlvTag.Signature) + ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt new file mode 100644 index 0000000000..f78e5663cb --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -0,0 +1,117 @@ +package com.tangem.commands + +import com.tangem.* +import com.tangem.common.CompletionResult +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.apdu.StatusWord +import com.tangem.common.apdu.toSessionError +import com.tangem.common.extensions.toInt +import com.tangem.common.tlv.TlvTag + +/** + * Basic interface for a parsed response from [Command]. + */ +interface CommandResponse + +/** + * Basic class for Tangem card commands + */ +abstract class Command : CardSessionRunnable { + + /** + * Serializes data into an array of [com.tangem.common.tlv.Tlv], + * then creates [CommandApdu] with this data. + * @param environment [SessionEnvironment] of the current card + * @return command data converted to [CommandApdu] that allows to convert it to [ByteArray] + * that can be sent to a Tangem card + */ + abstract fun serialize(environment: SessionEnvironment): CommandApdu + + /** + * Deserializes data received from a card and stored in [ResponseApdu] + * into an array of [com.tangem.common.tlv.Tlv]. Then maps it into a [CommandResponse]. + * @param environment [SessionEnvironment] of the current card. + * @param apdu received data. + * @return Card response converted to a [CommandResponse] of a type [T] + */ + abstract fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + Log.i("Command", "Sending ${this::class.java.simpleName}") + transceive(session, callback) + } + + fun transceive(session: CardSession, callback: (result: CompletionResult) -> Unit) { + try { + val apdu = serialize(session.environment) + transceiveApdu(apdu, session) { result -> + when (result) { + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + is CompletionResult.Success -> { + val response = deserialize(session.environment, result.data) + callback(CompletionResult.Success(response)) + } + } + } + } catch (error: SessionError) { + callback(CompletionResult.Failure(error)) + } + } + + private fun transceiveApdu(apdu: CommandApdu, session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.send(apdu) { result -> + + when (result) { + is CompletionResult.Success -> { + val responseApdu = result.data + when (responseApdu.statusWord) { + StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged + -> callback(CompletionResult.Success(responseApdu)) + StatusWord.NeedPause -> { + // NeedPause is returned from the card whenever security delay is triggered. + val remainingTime = deserializeSecurityDelay(responseApdu, session.environment) + if (remainingTime != null) { + session.viewDelegate.onSecurityDelay( + remainingTime, + session.environment.card?.pauseBeforePin2 ?: 0) + } + Log.i(this::class.simpleName!!, "Nfc command ${this::class.simpleName!!} " + + "triggered security delay of $remainingTime milliseconds") + transceiveApdu(apdu, session, callback) + } + else -> { + val error = responseApdu.statusWord.toSessionError() + if (error != null && !tryHandleError(error)) { + callback(CompletionResult.Failure(error)) + } else { + callback(CompletionResult.Failure(SessionError.UnknownError())) + } + } + } + } + is CompletionResult.Failure -> + if (result.error == SessionError.TagLost()) { + session.viewDelegate.onTagLost() + } else { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + /** + * Helper method to parse security delay information received from a card. + * + * @return Remaining security delay in milliseconds. + */ + private fun deserializeSecurityDelay(responseApdu: ResponseApdu, environment: SessionEnvironment): Int? { + val tlv = responseApdu.getTlvData() + return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() + } + + private fun tryHandleError(error: SessionError): Boolean { + return false + } + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt deleted file mode 100644 index 134474fb23..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.commands - -import com.tangem.common.CardEnvironment -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.toInt -import com.tangem.common.tlv.TlvTag - -/** - * Simple interface for responses received after sending commands to Tangem cards. - */ -interface CommandResponse - -/** - * Abstract class for all Tangem card commands. - */ -abstract class CommandSerializer { - - /** - * Serializes data into a [List] of [com.tangem.common.tlv.Tlv], - * then creates [CommandApdu] with this data. - * - * @return Command data that can be converted to raw bytes with a method [CommandApdu.toBytes]. - */ - abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu - - /** - * Deserializes data, received from a card and stored in [ResponseApdu], - * into a [List] of [com.tangem.common.tlv.Tlv]. Then this method maps it into a [CommandResponse]. - * - * @return Card response, converted to a [CommandResponse] of a type [T]. - */ - abstract fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): T? - - /** - * Helper method to parse security delay information received from a card. - * - * @return Remaining security delay in milliseconds. - */ - fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData() - return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 18b20f1394..089a134702 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class CreateWalletResponse( /** @@ -35,32 +35,29 @@ class CreateWalletResponse( * * @property cardId CID, Unique Tangem card ID number. */ -class CreateWalletCommand : CommandSerializer() { +class CreateWalletCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Cvc, environment.cvc) return CommandApdu( Instruction.CreateWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - CreateWalletResponse( - cardId = mapper.map(TlvTag.CardId), - status = mapper.map(TlvTag.Status), - walletPublicKey = mapper.map(TlvTag.WalletPublicKey) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return CreateWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + status = decoder.decode(TlvTag.Status), + walletPublicKey = decoder.decode(TlvTag.WalletPublicKey) + ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 85d3ddf866..7484f9d48b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class OpenSessionResponse( val sessionKeyB: ByteArray, @@ -20,27 +20,24 @@ class OpenSessionResponse( * to encrypt and decrypt commands’ payload. */ -class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { +class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) return CommandApdu( Instruction.OpenSession, tlvBuilder.serialize(), - encryptionMode = cardEnvironment.encryptionMode + encryptionMode = environment.encryptionMode ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): OpenSessionResponse { + val tlvData = apdu.getTlvData() + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - OpenSessionResponse( - sessionKeyB = mapper.map(TlvTag.SessionKeyB), - uid = mapper.map(TlvTag.Uid) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return OpenSessionResponse( + sessionKeyB = decoder.decode(TlvTag.SessionKeyB), + uid = decoder.decode(TlvTag.Uid) + ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index 4eb379e521..057ae8d084 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class PurgeWalletResponse( /** @@ -27,29 +27,26 @@ class PurgeWalletResponse( * ‘Purged’ state is final, it makes the card useless. * @property cardId CID, Unique Tangem card ID number. */ -class PurgeWalletCommand : CommandSerializer() { +class PurgeWalletCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) return CommandApdu( Instruction.PurgeWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - PurgeWalletResponse( - cardId = mapper.map(TlvTag.CardId), - status = mapper.map(TlvTag.Status)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return PurgeWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + status = decoder.decode(TlvTag.Status)) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index c861d73469..09c188072d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -1,14 +1,14 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError import java.util.* /** @@ -73,7 +73,7 @@ data class SigningMethod(val rawValue: Int) { if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData if (signPos) signingMethod += 0x01 shl SigningMethod.signPos } - return SigningMethod(signingMethod) + return SigningMethod(signingMethod) } } } @@ -354,6 +354,13 @@ class Card( */ val userCounter: Int?, + /** + * This value can be initialized by App (with PIN2 confirmation) and will be increased by COS + * with the execution of each [SignCommand]. For example, this field can store blockchain “nonce” + * for a quick one-touch transaction on POS terminals. Returned only if [SigningMethod.SignPos]. + */ + val userProtectedCounter: Int?, + /** * When this value is true, it means that the application is linked to the card, * and COS will not enforce security delay if [SignCommand] will be called @@ -373,58 +380,56 @@ class Card( * This command receives from the Tangem Card all the data about the card and the wallet, * including unique card number (CID or cardId) that has to be submitted while calling all other commands. */ -class ReadCommand : CommandSerializer() { +class ReadCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() /** - * [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain + * [SessionEnvironment] stores the pin1 value. If no pin1 value was set, it will contain * default value of ‘000000’. * In order to obtain card’s data, [ReadCommand] should use the correct pin 1 value. * The card will not respond if wrong pin 1 has been submitted. */ - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) return CommandApdu( Instruction.Read, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val tlvMapper = TlvMapper(tlvData) + val decoder = TlvDecoder(tlvData) - Card( - cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", - manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", - status = tlvMapper.mapOptional(TlvTag.Status), + return Card( + cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", + manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", + status = decoder.decodeOptional(TlvTag.Status), - firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), - cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), - settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), - issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), - curve = tlvMapper.mapOptional(TlvTag.CurveId), - maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), - signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), - pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), - walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), - walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), - health = tlvMapper.mapOptional(TlvTag.Health), - isActivated = tlvMapper.map(TlvTag.IsActivated), - activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), - paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), - userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), - terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + firmwareVersion = decoder.decodeOptional(TlvTag.Firmware), + cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey), + settingsMask = decoder.decodeOptional(TlvTag.SettingsMask), + issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey), + curve = decoder.decodeOptional(TlvTag.CurveId), + maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures), + signingMethod = decoder.decodeOptional(TlvTag.SigningMethod), + pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2), + walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes), + health = decoder.decodeOptional(TlvTag.Health), + isActivated = decoder.decode(TlvTag.IsActivated), + activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed), + paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion), + userCounter = decoder.decodeOptional(TlvTag.UserCounter), + userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter), + terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked), - cardData = deserializeCardData(tlvData) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + cardData = deserializeCardData(tlvData) + ) } private fun deserializeCardData(tlvData: List): CardData? { @@ -433,18 +438,18 @@ class ReadCommand : CommandSerializer() { } if (cardDataTlvs.isNullOrEmpty()) return null - val tlvMapper = TlvMapper(cardDataTlvs) + val decoder = TlvDecoder(cardDataTlvs) return CardData( - batchId = tlvMapper.mapOptional(TlvTag.Batch), - manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), - issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), - blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), - manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), - productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + batchId = decoder.decodeOptional(TlvTag.Batch), + manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime), + issuerName = decoder.decodeOptional(TlvTag.IssuerId), + blockchainName = decoder.decodeOptional(TlvTag.BlockchainId), + manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature), + productMask = decoder.decodeOptional(TlvTag.ProductMask), - tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), - tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), - tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol), + tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), + tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index cb95fd0899..7055bc403f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class ReadIssuerDataResponse( @@ -51,34 +54,63 @@ class ReadIssuerDataResponse( * @property cardId CID, Unique Tangem card ID number. */ class ReadIssuerDataCommand( + val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), - IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + super.run(session) { result -> + when (result) { + is CompletionResult.Failure -> callback(result) + is CompletionResult.Success -> { + if (result.data.issuerData.isEmpty()) { + callback(result) + return@run + } + val issuerDataToVerify = IssuerDataToVerify( + card.cardId, result.data.issuerData, result.data.issuerDataCounter + ) + if (verify(publicKey, result.data.issuerDataSignature, issuerDataToVerify)) { + callback(result) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + } + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) return CommandApdu( Instruction.ReadIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId), - issuerData = mapper.map(TlvTag.IssuerData), - issuerDataSignature = mapper.map(TlvTag.IssuerDataSignature), - issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return ReadIssuerDataResponse( + cardId = decoder.decode(TlvTag.CardId), + issuerData = decoder.decode(TlvTag.IssuerData), + issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature), + issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter) + ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 5672003477..4140000621 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -1,16 +1,20 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError +import java.io.ByteArrayOutputStream class ReadIssuerExtraDataResponse( @@ -56,38 +60,113 @@ class ReadIssuerExtraDataResponse( * a series of these commands have to be executed to read the entire Issuer_Extra_Data. */ class ReadIssuerExtraDataCommand( + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - var offset: Int = 0 + private val issuerData = ByteArrayOutputStream() + private var offset: Int = 0 + private var issuerDataSize: Int = 0 - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + readIssuerData(session, card.cardId, publicKey, callback) + } + + + private fun readIssuerData( + session: CardSession, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit) { + + if (issuerDataSize != 0) { + session.viewDelegate.onDelay( + issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + ) + } + + transceive(session) { result -> + when (result) { + is CompletionResult.Success -> { + if (result.data.size != null) { + if (result.data.size == 0) { + callback(CompletionResult.Success(result.data)) + return@transceive + } + issuerDataSize = result.data.size + } + issuerData.write(result.data.issuerData) + if (result.data.issuerDataSignature == null) { + offset = issuerData.size() + readIssuerData(session, cardId, publicKey, callback) + } else { + completeTask(result.data, cardId, publicKey, callback) + } + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + private fun completeTask(data: ReadIssuerExtraDataResponse, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit) { + val dataToVerify = IssuerDataToVerify( + cardId, + issuerData.toByteArray(), + data.issuerDataCounter + ) + if (verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { + val finalResult = ReadIssuerExtraDataResponse( + data.cardId, + issuerDataSize, + issuerData.toByteArray(), + data.issuerDataSignature, + data.issuerDataCounter + ) + callback(CompletionResult.Success(finalResult)) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) return CommandApdu( Instruction.ReadIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadIssuerExtraDataResponse( - cardId = mapper.map(TlvTag.CardId), - size = mapper.mapOptional(TlvTag.Size), - issuerData = mapper.mapOptional(TlvTag.IssuerData) ?: byteArrayOf(), - issuerDataSignature = mapper.mapOptional(TlvTag.IssuerDataSignature), - issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + + val decoder = TlvDecoder(tlvData) + return ReadIssuerExtraDataResponse( + cardId = decoder.decode(TlvTag.CardId), + size = decoder.decodeOptional(TlvTag.Size), + issuerData = decoder.decodeOptional(TlvTag.IssuerData) ?: byteArrayOf(), + issuerDataSignature = decoder.decodeOptional(TlvTag.IssuerDataSignature), + issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter) + ) } companion object { diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 656c1750f0..528ba98fb0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class ReadUserDataResponse( /** @@ -47,33 +47,30 @@ class ReadUserDataResponse( * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. * For example, this fields may contain blockchain nonce value. */ -class ReadUserDataCommand: CommandSerializer() { +class ReadUserDataCommand: Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val builder = TlvBuilder() - builder.append(TlvTag.CardId, cardEnvironment.cardId) - builder.append(TlvTag.Pin, cardEnvironment.pin1) + builder.append(TlvTag.CardId, environment.card?.cardId) + builder.append(TlvTag.Pin, environment.pin1) return CommandApdu( Instruction.ReadUserData, builder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadUserDataResponse( - cardId = mapper.map(TlvTag.CardId), - userData = mapper.map(TlvTag.UserData), - userProtectedData = mapper.map(TlvTag.UserProtectedData), - userCounter = mapper.map(TlvTag.UserCounter), - userProtectedCounter = mapper.map(TlvTag.UserProtectedCounter) + val decoder = TlvDecoder(tlvData) + return ReadUserDataResponse( + cardId = decoder.decode(TlvTag.CardId), + userData = decoder.decode(TlvTag.UserData), + userProtectedData = decoder.decode(TlvTag.UserProtectedData), + userCounter = decoder.decode(TlvTag.UserCounter), + userProtectedCounter = decoder.decode(TlvTag.UserProtectedCounter) ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index 6f0b7bfacc..7707f82197 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -1,14 +1,14 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.sign -import com.tangem.tasks.TaskError /** * @param cardId CID, Unique Tangem card ID number @@ -31,10 +31,26 @@ class SignResponse( * @property cardId CID, Unique Tangem card ID number */ class SignCommand(private val hashes: Array) - : CommandSerializer() { + : Command() { private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 - private val dataToSign = flattenHashes() + + override fun serialize(environment: SessionEnvironment): CommandApdu { + val dataToSign = flattenHashes() + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) + tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) + tlvBuilder.append(TlvTag.Cvc, environment.cvc) + + addTerminalSignature(environment, dataToSign, tlvBuilder) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + environment.encryptionMode, environment.encryptionKey + ) + } private fun flattenHashes(): ByteArray { checkForErrors() @@ -42,25 +58,9 @@ class SignCommand(private val hashes: Array) } private fun checkForErrors() { - if (hashes.isEmpty()) throw TaskError.EmptyHashes() - if (hashes.size > 10) throw TaskError.TooMuchHashesInOneTransaction() - if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual() - } - - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) - tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) - tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - - addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu( - Instruction.Sign, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey - ) + if (hashes.isEmpty()) throw SessionError.EmptyHashes() + if (hashes.size > 10) throw SessionError.TooMuchHashesInOneTransaction() + if (hashes.any { it.size != hashSizes }) throw SessionError.HashSizeMustBeEqual() } /** @@ -70,23 +70,25 @@ class SignCommand(private val hashes: Array) * TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey * (this key should be generated and securily stored by the application). */ - private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvBuilder: TlvBuilder) { - cardEnvironment.terminalKeys?.let { terminalKeyPair -> + private fun addTerminalSignature( + environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder) { + environment.terminalKeys?.let { terminalKeyPair -> val signedData = dataToSign.sign(terminalKeyPair.privateKey) tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData) tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey) } } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - val tlvMapper = TlvMapper(tlvData) + val decoder = TlvDecoder(tlvData) return SignResponse( - cardId = tlvMapper.map(TlvTag.CardId), - signature = tlvMapper.map(TlvTag.Signature), - walletRemainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.map(TlvTag.SignedHashes) + cardId = decoder.decode(TlvTag.CardId), + signature = decoder.decode(TlvTag.Signature), + walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decode(TlvTag.SignedHashes) ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index dbc7f15640..a425f5a740 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class WriteIssuerDataResponse( /** @@ -33,13 +36,50 @@ class WriteIssuerDataCommand( private val issuerData: ByteArray, private val issuerDataSignature: ByteArray, private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + if (!isCounterValid(issuerDataCounter, card)) { + callback(CompletionResult.Failure(SessionError.MissingCounter())) + } else if (!verifySignature(publicKey, card.cardId)) { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } else { + super.run(session, callback) + } + } + + private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = + if (isCounterRequired(card)) issuerDataCounter != null else true + + private fun isCounterRequired(card: Card): Boolean = + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + + private fun verifySignature(publicKey: ByteArray, cardId: String): Boolean { + return verify( + publicKey, + issuerDataSignature, + IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + ) + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) tlvBuilder.append(TlvTag.IssuerData, issuerData) tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) @@ -47,20 +87,17 @@ class WriteIssuerDataCommand( return CommandApdu( Instruction.WriteIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - WriteIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return WriteIssuerDataResponse( + cardId = decoder.decode(TlvTag.CardId) + ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 753607f0a9..4a93ebd734 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError /** * This command writes Issuer Extra Data field and its issuer’s signature. @@ -33,17 +36,94 @@ class WriteIssuerExtraDataCommand( private val startingSignature: ByteArray, private val finalizingSignature: ByteArray, private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData var offset: Int = 0 - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + if (!isCounterValid(issuerDataCounter, card)) { + callback(CompletionResult.Failure(SessionError.MissingCounter())) + } else if (!verifySignatures(card.cardId, publicKey)) { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } else { + writeIssuerData(session, card.cardId, publicKey, callback) + } + } + + private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = + if (isCounterRequired(card)) issuerDataCounter != null else true + + private fun isCounterRequired(card: Card): Boolean = + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + + private fun verifySignatures(cardId: String, publicKey: ByteArray): Boolean { + + val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) + val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + + return verify(publicKey, startingSignature, firstData) && + verify(publicKey, finalizingSignature, secondData) + } + + private fun writeIssuerData( + session: CardSession, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit + ) { + + if (mode == IssuerDataMode.WriteExtraData) { + session.viewDelegate.onDelay(issuerData.size, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) + } + transceive(session) { result -> + when (result) { + is CompletionResult.Success -> { + when (mode) { + IssuerDataMode.InitializeWritingExtraData -> { + mode = IssuerDataMode.WriteExtraData + writeIssuerData(session, cardId, publicKey, callback) + return@transceive + } + IssuerDataMode.WriteExtraData -> { + offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + if (offset >= issuerData.size) { + mode = IssuerDataMode.FinalizeExtraData + } + writeIssuerData(session, cardId, publicKey, callback) + return@transceive + } + IssuerDataMode.FinalizeExtraData -> { + callback(CompletionResult.Success(result.data)) + } + } + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + + + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, mode) when (mode) { @@ -62,7 +142,7 @@ class WriteIssuerExtraDataCommand( } return CommandApdu( Instruction.WriteIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } @@ -74,17 +154,12 @@ class WriteIssuerExtraDataCommand( return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - WriteIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) + ) } companion object { diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index ac79cba45c..e9771d0b87 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -1,20 +1,20 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class WriteUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String -): CommandResponse + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String +) : CommandResponse /** * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. @@ -29,33 +29,29 @@ class WriteUserDataResponse( * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ class WriteUserDataCommand(private val userData: ByteArray? = null, private val userProtectedData: ByteArray? = null, - private val userCounter: Int? = null, - private val userProtectedCounter: Int? = null): CommandSerializer() { + private val userCounter: Int? = null, + private val userProtectedCounter: Int? = null) : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val builder = TlvBuilder() - builder.append(TlvTag.CardId, cardEnvironment.cardId) - builder.append(TlvTag.Pin, cardEnvironment.pin1) - builder.append(TlvTag.UserData, userData) - builder.append(TlvTag.UserCounter, userCounter) - builder.append(TlvTag.UserProtectedData, userProtectedData) - builder.append(TlvTag.UserProtectedCounter, userProtectedCounter) - if (userProtectedCounter != null || userProtectedData != null) - builder.append(TlvTag.Pin2, cardEnvironment.pin2) + override fun serialize(environment: SessionEnvironment): CommandApdu { + val builder = TlvBuilder() + builder.append(TlvTag.CardId, environment.card?.cardId) + builder.append(TlvTag.Pin, environment.pin1) + builder.append(TlvTag.UserData, userData) + builder.append(TlvTag.UserCounter, userCounter) + builder.append(TlvTag.UserProtectedData, userProtectedData) + builder.append(TlvTag.UserProtectedCounter, userProtectedCounter) + if (userProtectedCounter != null || userProtectedData != null) + builder.append(TlvTag.Pin2, environment.pin2) - return CommandApdu( - Instruction.WriteUserData, builder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey - ) - } - - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null - - return try { - WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + environment.encryptionMode, environment.encryptionKey + ) + } + + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() + return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId)) } - } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt index 034b28dce5..2ed9366331 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -1,8 +1,8 @@ package com.tangem.commands.personalization +import com.tangem.SessionEnvironment +import com.tangem.commands.Command import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -14,17 +14,16 @@ data class DepersonalizeResponse(val success: Boolean) : CommandResponse * * This command resets card to initial state, * erasing all data written during personalization and usage. - * @param cardId CID, Unique Tangem card ID number. */ -class DepersonalizeCommand : CommandSerializer() { +class DepersonalizeCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Depersonalize, byteArrayOf() ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? { + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): DepersonalizeResponse { return DepersonalizeResponse(true) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 035963c21a..24a118322a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -1,10 +1,11 @@ package com.tangem.commands.personalization +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.Card import com.tangem.commands.CardData -import com.tangem.commands.CommandSerializer +import com.tangem.commands.Command import com.tangem.commands.personalization.entities.* -import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -12,10 +13,9 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.sign -import com.tangem.tasks.TaskError /** * Command available on SDK cards only @@ -34,9 +34,9 @@ class PersonalizeCommand( private val config: CardConfig, private val issuer: Issuer, private val manufacturer: Manufacturer, private val acquirer: Acquirer? = null -) : CommandSerializer() { +) : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Personalize, serializePersonalizationData(config), @@ -44,39 +44,37 @@ class PersonalizeCommand( ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData(devPersonalizationKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val tlvMapper = TlvMapper(tlvData) - Card( - cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", - manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", - status = tlvMapper.mapOptional(TlvTag.Status), + val decoder = TlvDecoder(tlvData) + return Card( + cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", + manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", + status = decoder.decodeOptional(TlvTag.Status), - firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), - cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), - settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), - issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), - curve = tlvMapper.mapOptional(TlvTag.CurveId), - maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), - signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), - pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), - walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), - walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), - health = tlvMapper.mapOptional(TlvTag.Health), - isActivated = tlvMapper.map(TlvTag.IsActivated), - activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), - paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), - userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), - terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + firmwareVersion = decoder.decodeOptional(TlvTag.Firmware), + cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey), + settingsMask = decoder.decodeOptional(TlvTag.SettingsMask), + issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey), + curve = decoder.decodeOptional(TlvTag.CurveId), + maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures), + signingMethod = decoder.decodeOptional(TlvTag.SigningMethod), + pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2), + walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes), + health = decoder.decodeOptional(TlvTag.Health), + isActivated = decoder.decode(TlvTag.IsActivated), + activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed), + paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion), + userCounter = decoder.decodeOptional(TlvTag.UserCounter), + userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter), + terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked), - cardData = deserializeCardData(tlvData) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + cardData = deserializeCardData(tlvData) + ) } private fun deserializeCardData(tlvData: List): CardData? { @@ -85,23 +83,23 @@ class PersonalizeCommand( } if (cardDataTlvs.isNullOrEmpty()) return null - val tlvMapper = TlvMapper(cardDataTlvs) + val decoder = TlvDecoder(cardDataTlvs) return CardData( - batchId = tlvMapper.mapOptional(TlvTag.Batch), - manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), - issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), - blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), - manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), - productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + batchId = decoder.decodeOptional(TlvTag.Batch), + manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime), + issuerName = decoder.decodeOptional(TlvTag.IssuerId), + blockchainName = decoder.decodeOptional(TlvTag.BlockchainId), + manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature), + productMask = decoder.decodeOptional(TlvTag.ProductMask), - tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), - tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), - tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol), + tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), + tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) ) } private fun serializePersonalizationData(config: CardConfig): ByteArray { - val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError() + val cardId = config.createCardId() ?: throw SessionError.SerializeCommandError() val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.CardId, cardId) diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt index cda239b145..ec4cff04b0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Acquirer( val keyPair: KeyPair, diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt index f82889be6a..ee7ab58fa7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Issuer( val name: String, diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt index bffdde9384..9e30287bb0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Manufacturer( val keyPair: KeyPair, diff --git a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt b/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt index afd76a39e8..71f460c7d7 100644 --- a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt +++ b/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt @@ -1,7 +1,7 @@ package com.tangem.common +import com.tangem.SessionError import com.tangem.common.CompletionResult.Success -import com.tangem.tasks.TaskError /** * Response class encapsulating successful and failed results. @@ -9,5 +9,5 @@ import com.tangem.tasks.TaskError */ sealed class CompletionResult { class Success(val data: T) : CompletionResult() - class Failure(val error: TaskError) : CompletionResult() + class Failure(val error: SessionError) : CompletionResult() } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt index d389c172b3..3ba365247d 100644 --- a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt +++ b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt @@ -1,9 +1,11 @@ package com.tangem.common +import com.tangem.KeyPair + /** * Interface for a service for managing Terminal keypair, used for Linked Terminal feature. - * Its implementation Needs to be provided to [com.tangem.CardManager] - * by calling [com.tangem.CardManager.setTerminalKeysService]. + * Its implementation Needs to be provided to [com.tangem.TangemSdk] + * by calling [com.tangem.TangemSdk.setTerminalKeysService]. * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. * Linked Terminal feature can be disabled manually by editing [com.tangem.Config]. */ diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index 1a2c2be076..e2d37df4bb 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -1,6 +1,6 @@ package com.tangem.common.apdu -import com.tangem.common.EncryptionMode +import com.tangem.EncryptionMode import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.extensions.toByteArray import com.tangem.crypto.encrypt diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt index a43cfbe969..3938cee65f 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt @@ -1,5 +1,7 @@ package com.tangem.common.apdu +import com.tangem.SessionError + /** * Part of a response from the card, shows the status of the operation */ @@ -21,5 +23,18 @@ enum class StatusWord(val code: Int, val description: String) { private val values = values() fun byCode(code: Int): StatusWord = values.find { it.code == code } ?: Unknown } - +} + +fun StatusWord.toSessionError(): SessionError? { + return when (this) { + StatusWord.ProcessCompleted, StatusWord.Pin1Changed, + StatusWord.Pin2Changed, StatusWord.PinsChanged -> null + StatusWord.NeedPause -> null + StatusWord.InvalidParams -> SessionError.InvalidParams() + StatusWord.ErrorProcessingCommand -> SessionError.ErrorProcessingCommand() + StatusWord.InvalidState -> SessionError.InvalidState() + StatusWord.InsNotSupported -> SessionError.InsNotSupported() + StatusWord.NeedEncryption -> SessionError.NeedEncryption() + StatusWord.Unknown -> SessionError.UnknownStatus() + } } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt index 544317c358..5b0f5a47c5 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt @@ -1,6 +1,7 @@ package com.tangem.common.tlv import com.tangem.Log +import com.tangem.common.extensions.toHexString import java.io.ByteArrayInputStream import java.io.IOException @@ -71,6 +72,10 @@ class Tlv { } } + override fun toString(): String { + return "${this.tag} ($tagRaw): ${value.toHexString()}" + } + } fun List.serialize(): ByteArray = diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt index 6ec643d00a..3e3c8557f2 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.common.tlv +import com.tangem.Log + class TlvBuilder { private val tlvs = mutableListOf() private val encoder = TlvEncoder() @@ -10,6 +12,10 @@ class TlvBuilder { tlvs.add(encoder.encode(tag, value)) } - fun serialize(): ByteArray = tlvs.serialize() + fun serialize(): ByteArray { + Log.v("TLV", + "List of TLV that are being sent to a card:\n${tlvs.joinToString("\n")}") + return tlvs.serialize() + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt similarity index 85% rename from tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt rename to tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt index 1713fd2e2d..48c14edec4 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt @@ -1,13 +1,13 @@ package com.tangem.common.tlv import com.tangem.Log +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.toDate import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toInt import com.tangem.common.extensions.toUtf8 -import com.tangem.tasks.TaskError import java.util.* /** @@ -16,7 +16,12 @@ import java.util.* * * @property tlvList List of TLVs, which values are to be converted to particular classes. */ -class TlvMapper(val tlvList: List) { +class TlvDecoder(val tlvList: List) { + + init { + Log.v("TLV", + "List of TLV received from card:\n${tlvList.joinToString("\n")}") + } /** * Finds [Tlv] by its [TlvTag]. @@ -26,10 +31,10 @@ class TlvMapper(val tlvList: List) { * * @return Value converted to a nullable type [T]. */ - inline fun mapOptional(tag: TlvTag): T? = + inline fun decodeOptional(tag: TlvTag): T? = try { - map(tag) - } catch (exception: TaskError.MissingTag) { + decode(tag) + } catch (exception: SessionError.DecodingFailedMissingTag) { null } @@ -44,13 +49,13 @@ class TlvMapper(val tlvList: List) { * * @throws [TaskError.MissingTag] exception if no [Tlv] is found by the Tag. */ - inline fun map(tag: TlvTag): T { + inline fun decode(tag: TlvTag): T { val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value ?: if (tag.valueType() == TlvValueType.BoolValue && T::class == Boolean::class) { return false as T } else { Log.e(this::class.simpleName!!, "Tag $tag not found") - throw TaskError.MissingTag() + throw SessionError.DecodingFailedMissingTag() } return when (tag.valueType()) { @@ -68,7 +73,7 @@ class TlvMapper(val tlvList: List) { tlvValue.toInt() as T } catch (exception: IllegalArgumentException) { Log.e(this::class.simpleName!!, exception.message ?: "") - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.BoolValue -> { @@ -85,7 +90,7 @@ class TlvMapper(val tlvList: List) { EllipticCurve.byName(tlvValue.toUtf8()) as T } catch (exception: Exception) { logException(tag, tlvValue.toUtf8(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } @@ -96,7 +101,7 @@ class TlvMapper(val tlvList: List) { tlvValue.toDate() as T } catch (exception: Exception) { logException(tag, tlvValue.toHexString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.ProductMask -> { @@ -113,7 +118,7 @@ class TlvMapper(val tlvList: List) { CardStatus.byCode(tlvValue.toInt()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.SigningMethod -> { @@ -122,7 +127,7 @@ class TlvMapper(val tlvList: List) { SigningMethod(tlvValue.toInt()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.IssuerDataMode -> { @@ -131,7 +136,7 @@ class TlvMapper(val tlvList: List) { IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } } @@ -146,7 +151,7 @@ class TlvMapper(val tlvList: List) { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, "Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") - throw TaskError.WrongType() + throw SessionError.DecodingFailedTypeMismatch() } } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt index 58e4043450..1855badcab 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -1,13 +1,12 @@ package com.tangem.common.tlv import com.tangem.Log +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toByteArray -import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskError import java.util.* /** @@ -25,7 +24,7 @@ class TlvEncoder { return Tlv(tag, encodeValue(tag, value)) } else { Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null") - throw TaskError.SerializeCommandError() + throw SessionError.EncodingFailed() } } @@ -107,7 +106,7 @@ class TlvEncoder { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, "Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") - throw TaskError.WrongType() + throw SessionError.EncodingFailedTypeMismatch() } } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index 44200cd20a..e3c9122c59 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -125,7 +125,7 @@ enum class TlvTag(val code: Int) { fun valueType(): TlvValueType { return when (this) { CardId, Batch, CrExKey -> TlvValueType.HexString - Pin, Pin2, NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash + NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve diff --git a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt new file mode 100644 index 0000000000..6b21be102c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt @@ -0,0 +1,43 @@ +package com.tangem.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionError +import com.tangem.commands.CardStatus +import com.tangem.commands.CheckWalletCommand +import com.tangem.commands.CreateWalletCommand +import com.tangem.commands.CreateWalletResponse +import com.tangem.common.CompletionResult + +class CreateWalletTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val curve = session.environment.card?.curve + if (curve == null) { + callback(CompletionResult.Failure(SessionError.CardError())) + return + } + + val command = CreateWalletCommand() + command.run(session) { createWalletResult -> + when (createWalletResult) { + is CompletionResult.Failure -> callback(createWalletResult) + is CompletionResult.Success -> { + if (createWalletResult.data.status != CardStatus.Loaded) { + callback(CompletionResult.Failure(SessionError.UnknownError())) + } else { + val checkWalletCommand = CheckWalletCommand( + curve, createWalletResult.data.walletPublicKey + ) + checkWalletCommand.run(session) { result -> + when (result) { + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + is CompletionResult.Success -> callback(createWalletResult) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt deleted file mode 100644 index d7c50dde31..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.ReadIssuerDataCommand -import com.tangem.commands.ReadIssuerDataResponse -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task() { - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - val command = ReadIssuerDataCommand() - - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - val data = result.data - if (data.issuerData.isEmpty()) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - return@sendCommand - } - val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!! - val issuerDataToVerify = IssuerDataToVerify( - cardEnvironment.cardId!!, - data.issuerData, - data.issuerDataCounter - ) - if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } else { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt deleted file mode 100644 index 4f1397b8db..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.ReadIssuerExtraDataCommand -import com.tangem.commands.ReadIssuerExtraDataResponse -import com.tangem.commands.WriteIssuerExtraDataCommand -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult -import java.io.ByteArrayOutputStream - -/** - * This task performs [ReadIssuerExtraDataCommand] repeatedly until - * the issuer extra data is fully retrieved. - */ -internal class ReadIssuerExtraDataTask( - private val issuerPublicKey: ByteArray?) : Task() { - - private val issuerData = ByteArrayOutputStream() - private var issuerDataSize = 0 - private lateinit var card: Card - private lateinit var cardEnvironment: CardEnvironment - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit - ) { - card = currentCard!! - this.cardEnvironment = cardEnvironment - val command = ReadIssuerExtraDataCommand() - readIssuerData(command, callback) - } - - private fun readIssuerData( - command: ReadIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - - if (issuerDataSize != 0) { - delegate?.onDelay( - issuerDataSize, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE - ) - } - - sendCommand(command, cardEnvironment) { result -> - when (result) { - - is CompletionResult.Success -> { - if (result.data.size != null) { - if (result.data.size == 0) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - return@sendCommand - } - issuerDataSize = result.data.size - } - issuerData.write(result.data.issuerData) - if (result.data.issuerDataSignature == null) { - command.offset = issuerData.size() - readIssuerData(command, callback) - } else { - completeTask(result.data, command, callback) - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - val publicKey = issuerPublicKey ?: card.issuerPublicKey!! - val dataToVerify = IssuerDataToVerify( - cardEnvironment.cardId!!, - issuerData.toByteArray(), - data.issuerDataCounter - ) - if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { - completeNfcSession() - val finalResult = ReadIssuerExtraDataResponse( - data.cardId, - issuerDataSize, - issuerData.toByteArray(), - data.issuerDataSignature, - data.issuerDataCounter - ) - callback(TaskEvent.Event(finalResult)) - callback(TaskEvent.Completion()) - } else { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index f0160c48b3..207cb6f705 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -1,78 +1,42 @@ package com.tangem.tasks +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionError import com.tangem.commands.* -import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult -/** - * Events that [ScanTask] returns on completion of its commands. - */ -sealed class ScanEvent { - - /** - * Contains data from a Tangem card after successful completion of [ReadCommand]. - */ - data class OnReadEvent(val card: Card) : ScanEvent() - - /** - * Shows whether the Tangem card was verified on completion of [CheckWalletCommand]. - */ - data class OnVerifyEvent(val isGenuine: Boolean) : ScanEvent() -} - /** * Task that allows to read Tangem card and verify its private key. * * It performs two commands, [ReadCommand] and [CheckWalletCommand], subsequently. */ -internal class ScanTask : Task() { +internal class ScanTask : CardSessionRunnable { - override fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard))) + val card = session.environment.card - if (currentCard == null) { - completeNfcSession(TaskError.MissingPreflightRead()) - callback(TaskEvent.Completion(TaskError.MissingPreflightRead())) + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) - } else if (currentCard.cardData?.productMask?.contains(ProductMask.tag) != false) { - completeNfcSession() - callback(TaskEvent.Completion()) + } else if (card.cardData?.productMask?.contains(ProductMask.tag) != false) { + callback(CompletionResult.Success(card)) - } else if (currentCard.status != CardStatus.Loaded) { - completeNfcSession() - callback(TaskEvent.Completion()) + } else if (card.status != CardStatus.Loaded) { + callback(CompletionResult.Success(card)) - } else if (currentCard.curve == null || currentCard.walletPublicKey == null) { - completeNfcSession(TaskError.CardError()) - callback(TaskEvent.Completion(TaskError.CardError())) + } else if (card.curve == null || card.walletPublicKey == null) { + callback(CompletionResult.Failure(SessionError.CardError())) } else { + val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) - val checkWalletCommand = CheckWalletCommand() - - sendCommand(checkWalletCommand, cardEnvironment) { result -> + checkWalletCommand.run(session) { result -> when (result) { - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - is CompletionResult.Success -> { - completeNfcSession() - val verified = result.data.verify( - currentCard.curve, - currentCard.walletPublicKey, - checkWalletCommand.challenge - ) - callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified))) - callback(TaskEvent.Completion()) - } + is CompletionResult.Success -> callback(CompletionResult.Success(card)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } - } } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt deleted file mode 100644 index 03efa615f8..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.common.CardEnvironment -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.common.CompletionResult - -/** - * Allows to perform a single command. - * - * @property command A command that will be performed. - */ -class SingleCommandTask( - private val command: CommandSerializer -) : Task() { - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit - ) { - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } -} diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt deleted file mode 100644 index 224b163a9a..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ /dev/null @@ -1,358 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardManagerDelegate -import com.tangem.CardReader -import com.tangem.Log -import com.tangem.commands.* -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult -import com.tangem.common.EncryptionMode -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.StatusWord -import com.tangem.common.extensions.calculateSha256 -import com.tangem.crypto.EncryptionHelper -import com.tangem.crypto.FastEncryptionHelper -import com.tangem.crypto.StrongEncryptionHelper -import com.tangem.crypto.pbkdf2Hash - -/** - * An error class that represent typical errors that may occur when performing Tangem SDK tasks. - * Errors are propagated back to the caller in callbacks. - */ -sealed class TaskError(val code: Int) : Exception() { - - //Errors in serializing APDU - /** - * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] - * (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors). - */ - class SerializeCommandError : TaskError(1001) - - class EncodingError : TaskError(1002) - class MissingTag : TaskError(1003) - class WrongType : TaskError(1004) - class ConvertError : TaskError(1005) - - /** - * This error is returned when unknown [StatusWord] is received from a card. - */ - class UnknownStatus : TaskError(2001) - - /** - * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. - * The card sends this status in case of internal card error. - */ - class ErrorProcessingCommand : TaskError(2002) - - /** - * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] - * is executed before performing other commands. - */ - class MissingPreflightRead : TaskError(2003) - - /** - * This error is returned when a card's reply is [StatusWord.InvalidState]. - * The card sends this status when command can not be executed in the current state of a card. - */ - class InvalidState : TaskError(2004) - - /** - * This error is returned when a card's reply is [StatusWord.InsNotSupported]. - * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. - */ - class InsNotSupported : TaskError(2005) - - /** - * This error is returned when a card's reply is [StatusWord.InvalidParams]. - * The card sends this status when there are wrong or not sufficient parameters in TLV request, - * or wrong PIN1/PIN2. - * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], - * mapping or serialization errors. - */ - class InvalidParams : TaskError(2006) - - /** - * This error is returned when a card's reply is [StatusWord.NeedEncryption] - * and the encryption was not established by TangemSdk. - */ - class NeedEncryption : TaskError(2007) - - //Scan errors - /** - * This error is returned when a [Task] checks unsuccessfully either - * a card's ability to sign with its private key, or the validity of issuer data. - */ - class VerificationFailed : TaskError(3000) - - /** - * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. - */ - class CardError : TaskError(3001) - - /** - * This error is returned when a [Task] expects a user to use a particular card, - * and a user tries to use a different card. - */ - class WrongCard : TaskError(3002) - - /** - * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. - * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. - */ - class TooMuchHashesInOneTransaction : TaskError(3003) - - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives only empty hashes for signature. - */ - class EmptyHashes : TaskError(3004) - - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives hashes of different lengths for signature. - */ - class HashSizeMustBeEqual : TaskError(3005) - - /** - * This error is returned when [com.tangem.CardManager] was called with a new [Task], - * while a previous [Task] is still in progress. - */ - class Busy : TaskError(4000) - - /** - * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. - */ - class UserCancelled : TaskError(4001) - - //NFC errors - class NfcReaderError : TaskError(5002) - - /** - * This error is returned when Android NFC reader loses a tag - * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. - */ - class TagLost : TaskError(5003) - - class UnknownError : TaskError(6000) - - //Specific Command Errors - /** - * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter - * (when the card's requires it), but the counter is missing. - */ - class MissingCounter : TaskError(7001) -} - -/** - * Events that are are sent in callbacks from [Task]. - */ -sealed class TaskEvent { - - /** - * A callback that is triggered by a Task. - */ - class Event(val data: T) : TaskEvent() - - /** - * A callback that is triggered when a [Task] is completed. - * - * @param error is null if it's a successful completion of a [Task] - */ - class Completion(val error: TaskError? = null) : TaskEvent() -} - -/** - * Allows to perform a group of commands interacting between the card and the application. - * A task opens an NFC session, sends commands to the card and receives its responses, - * repeats the commands if needed, and closes session after receiving the last answer. - */ -abstract class Task { - - var delegate: CardManagerDelegate? = null - var reader: CardReader? = null - var performPreflightRead: Boolean = true - var securityDelayDuration: Int = 0 - - /** - * This method should be called to run the [Task] and perform all its operations. - * - * @param cardEnvironment Relevant current version of a card environment - * @param callback It will be triggered during the performance of the [Task] - */ - fun run(cardEnvironment: CardEnvironment, - callback: (result: TaskEvent) -> Unit) { - delegate?.onNfcSessionStarted(cardEnvironment.cardId) - reader?.openSession() - Log.i(this::class.simpleName!!, "Nfc task is started") - - if (performPreflightRead) { - runWithPreflightRead(cardEnvironment, callback) - } else { - onRun(cardEnvironment, null, callback) - } - } - - /** - * Should be called on [Task] completion, whether it was successful or with failure. - * - * @param taskError The error to be shown by [CardManagerDelegate] - */ - protected fun completeNfcSession(taskError: TaskError? = null) { - reader?.closeSession() - if (taskError != null) { - delegate?.onError(taskError) - } else { - delegate?.onNfcSessionCompleted() - } - } - - /** - * In this method the individual Tasks' logic should be implemented. - */ - protected abstract fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) - - /** - * This method should be called by Tasks in their [onRun] method wherever - * they need to communicate with the Tangem Card by launching commands. - */ - protected fun sendCommand( - command: CommandSerializer, - cardEnvironment: CardEnvironment, - callback: (result: CompletionResult) -> Unit) { - - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - - when (cardEnvironment.encryptionMode) { - EncryptionMode.NONE -> { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - } - EncryptionMode.FAST, EncryptionMode.STRONG -> { - if (cardEnvironment.encryptionKey != null ) { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - return - } - val encryptionHelper: EncryptionHelper = - if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { - StrongEncryptionHelper() - } else { - FastEncryptionHelper() - } - val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) - val openSessionApdu = openSessionCommand.serialize(cardEnvironment) - sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - val uid = result.data.uid - val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) - val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) - val sessionKey = (secret + protocolKey).calculateSha256() - cardEnvironment.encryptionKey = sessionKey - - sendCommand(command, cardEnvironment, callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - } - } - - private fun sendRequest(command: CommandSerializer, - commandApdu: CommandApdu, - cardEnvironment: CardEnvironment, - callback: (result: CompletionResult) -> Unit) { - - reader?.transceiveApdu(commandApdu) { result -> - - when (result) { - is CompletionResult.Success -> { - val responseApdu = result.data - when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged - -> { - try { - val responseData = command.deserialize(cardEnvironment, responseApdu) - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is completed") - callback(CompletionResult.Success(responseData as T)) - } catch (error: TaskError) { - callback(CompletionResult.Failure(error)) - } - } - StatusWord.InvalidParams -> callback(CompletionResult.Failure(TaskError.InvalidParams())) - StatusWord.Unknown -> { - Log.e(this::class.simpleName!!, "Unknown status error: ${result.data.sw}") - callback(CompletionResult.Failure(TaskError.UnknownStatus())) - } - StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand())) - StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) - - StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> { - when (cardEnvironment.encryptionMode) { - EncryptionMode.NONE -> { - cardEnvironment.encryptionKey = null - cardEnvironment.encryptionMode = EncryptionMode.FAST - } - EncryptionMode.FAST -> { - cardEnvironment.encryptionKey = null - cardEnvironment.encryptionMode = EncryptionMode.STRONG - } - EncryptionMode.STRONG -> { - Log.e(this::class.simpleName!!, "Encryption doesn't work") - callback(CompletionResult.Failure(TaskError.NeedEncryption())) - return@transceiveApdu - } - } - sendCommand(command, cardEnvironment, callback) - } - StatusWord.NeedPause -> { - // NeedPause is returned from the card whenever security delay is triggered. - val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) - if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") - sendRequest(command, commandApdu, cardEnvironment, callback) - } - } - } - is CompletionResult.Failure -> - if (result.error == TaskError.TagLost()) { - delegate?.onTagLost() - } else if (result.error is TaskError.UserCancelled) { - callback(CompletionResult.Failure(TaskError.UserCancelled())) - reader?.closeSession() - } - } - } - } - - private fun runWithPreflightRead( - environment: CardEnvironment, callback: (result: TaskEvent) -> Unit) { - sendCommand(ReadCommand(), environment) { readResult -> - when (readResult) { - is CompletionResult.Failure -> { - completeNfcSession(readResult.error) - callback(TaskEvent.Completion(readResult.error)) - } - is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId - securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 - - if (environment.cardId != null && environment.cardId != receivedCardId) { - completeNfcSession(TaskError.WrongCard()) - callback(TaskEvent.Completion(TaskError.WrongCard())) - return@sendCommand - } - - val newEnvironment = environment.copy(cardId = receivedCardId) - onRun(newEnvironment, readResult.data, callback) - } - } - } - } -} - - diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt deleted file mode 100644 index b22ed95049..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.Settings -import com.tangem.commands.WriteIssuerDataCommand -import com.tangem.commands.WriteIssuerDataResponse -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -class WriteIssuerDataTask( - private val issuerData: ByteArray, - private val issuerDataSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null -) : Task() { - - private lateinit var card: Card - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - card = currentCard!! - val command = WriteIssuerDataCommand( - issuerData, issuerDataSignature, issuerDataCounter - ) - if (!isCounterValid(issuerDataCounter)) { - completeNfcSession(TaskError.MissingCounter()) - callback(TaskEvent.Completion(TaskError.MissingCounter())) - } else if (!verifySignature(command, cardEnvironment.cardId!!)) { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun isCounterValid(issuerDataCounter: Int?): Boolean = - if (isCounterRequired()) issuerDataCounter != null else true - - private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false - - private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean { - return command.verify( - issuerPublicKey ?: card.issuerPublicKey!!, - issuerDataSignature, - IssuerDataToVerify(cardId, issuerData, issuerDataCounter) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt deleted file mode 100644 index 887e832548..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.* -import com.tangem.commands.common.IssuerDataMode -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -/** - * This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully - * written on the card. - * @param issuerData Data provided by issuer. - * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], - * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and - * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. - * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], - * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay - * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). - * @param issuerDataCounter An optional counter that protect issuer data against replay attack. - */ -internal class WriteIssuerExtraDataTask( - private val issuerData: ByteArray, - private val startingSignature: ByteArray, - private val finalizingSignature: ByteArray, - private val issuerPublicKey: ByteArray? = null, - private val issuerDataCounter: Int? = null -) : Task() { - - private lateinit var card: Card - private lateinit var cardEnvironment: CardEnvironment - - override fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - card = currentCard!! - this.cardEnvironment = cardEnvironment - val command = WriteIssuerExtraDataCommand( - issuerData, startingSignature, finalizingSignature, issuerDataCounter - ) - if (!isCounterValid(issuerDataCounter)) { - completeNfcSession(TaskError.MissingCounter()) - callback(TaskEvent.Completion(TaskError.MissingCounter())) - } else if (!verifySignatures(command)) { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - - writeIssuerData(command, callback) - } - - private fun writeIssuerData( - command: WriteIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - - if (command.mode == IssuerDataMode.WriteExtraData) { - delegate?.onDelay(issuerData.size, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) - } - sendCommand(command, cardEnvironment) { result -> - when (result) { - - is CompletionResult.Success -> { - when (command.mode) { - IssuerDataMode.InitializeWritingExtraData -> { - command.mode = IssuerDataMode.WriteExtraData - writeIssuerData(command, callback) - return@sendCommand - } - IssuerDataMode.WriteExtraData -> { - command.offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE - if (command.offset >= issuerData.size) { - command.mode = IssuerDataMode.FinalizeExtraData - } - writeIssuerData(command, callback) - return@sendCommand - } - IssuerDataMode.FinalizeExtraData -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun isCounterValid(issuerDataCounter: Int?): Boolean = - if (isCounterRequired()) issuerDataCounter != null else true - - private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false - - private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean { - val publicKey = issuerPublicKey ?: card.issuerPublicKey!! - val cardId = cardEnvironment.cardId!! - - val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) - val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) - - return command.verify(publicKey, startingSignature, firstData) && - command.verify(publicKey, finalizingSignature, secondData) - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index 61d6ea71d3..f879f65e6f 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -1,9 +1,7 @@ package com.tangem.common.apdu import com.google.common.truth.Truth.assertThat -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.Tlv +import com.tangem.SessionEnvironment import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag import org.junit.Test @@ -13,9 +11,9 @@ class CommandApduTest { @Test fun `simple READ command to bytes`() { - val pin = "000000" + val sessionEnvironment = SessionEnvironment() val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, pin) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1) val commandApdu = CommandApdu( Instruction.Read, tlvBuilder.serialize() @@ -30,13 +28,13 @@ class CommandApduTest { @Test fun `READ with terminal key to bytes`() { - val pin = "000000" + val sessionEnvironment = SessionEnvironment() val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, pin) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey) val commandApdu = CommandApdu( Instruction.Read, diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt similarity index 71% rename from tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt rename to tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt index 64cb5ce15d..f9915fc7b8 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt @@ -1,69 +1,69 @@ package com.tangem.common.tlv import com.google.common.truth.Truth.assertThat +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.common.extensions.hexToBytes -import com.tangem.tasks.TaskError import org.junit.Test import org.junit.jupiter.api.assertThrows import java.util.* -class TlvMapperTest { +class TlvDecoderTest { private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0) private val tlvData = Tlv.deserialize(rawData) - private val tlvMapper = TlvMapper(tlvData!!) + private val tlvMapper = TlvDecoder(tlvData!!) - private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData) - private val cardDataMapper = TlvMapper(Tlv.deserialize(cardDataRaw)!!) + private val cardDataRaw: ByteArray = tlvMapper.decode(TlvTag.CardData) + private val cardDataMapper = TlvDecoder(Tlv.deserialize(cardDataRaw)!!) @Test fun `map optional when value is present`() { - val settingsMask: SettingsMask? = tlvMapper.mapOptional(TlvTag.SettingsMask) + val settingsMask: SettingsMask? = tlvMapper.decodeOptional(TlvTag.SettingsMask) assertThat(settingsMask) .isNotNull() } @Test fun `map optional when no tag returns null`() { - val tokenSymbol: String? = tlvMapper.mapOptional(TlvTag.TokenSymbol) + val tokenSymbol: String? = tlvMapper.decodeOptional(TlvTag.TokenSymbol) assertThat(tokenSymbol) .isNull() } @Test fun `map when value is null throws MissingTagException`() { - assertThrows { - tlvMapper.map(TlvTag.TokenSymbol) + assertThrows { + tlvMapper.decode(TlvTag.TokenSymbol) } } @Test fun `map optional to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.mapOptional(TlvTag.CardData) + assertThrows { + tlvMapper.decodeOptional(TlvTag.CardData) } } @Test fun `map to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.map(TlvTag.CardData) + assertThrows { + tlvMapper.decode(TlvTag.CardData) } } @Test fun `map boolean missing flag returns false`() { - val terminalIsLinked: Boolean = tlvMapper.map(TlvTag.TerminalIsLinked) + val terminalIsLinked: Boolean = tlvMapper.decode(TlvTag.TerminalIsLinked) assertThat(terminalIsLinked) .isFalse() } @Test fun `map SettingsMask returns correct value`() { - val settingsMask: SettingsMask = tlvMapper.map(TlvTag.SettingsMask) + val settingsMask: SettingsMask = tlvMapper.decode(TlvTag.SettingsMask) assertThat(settingsMask) .isNotNull() assertThat(settingsMask.rawValue) @@ -82,16 +82,16 @@ class TlvMapperTest { @Test fun `map SigningMethods single value returns correct value`() { - val signingMethods: SigningMethod = tlvMapper.map(TlvTag.SigningMethod) + val signingMethods: SigningMethod = tlvMapper.decode(TlvTag.SigningMethod) assertThat(signingMethods.contains(SigningMethod.signHash)) .isTrue() } @Test fun `map SigningMethods set of methods returns correct value`() { - val localMapper = TlvMapper(Tlv.deserialize("070195".hexToBytes())!!) + val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!) - val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod) + val signingMethod: SigningMethod = localMapper.decode(TlvTag.SigningMethod) assertThat(signingMethod.contains(SigningMethod.signHash)) .isTrue() assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer)) @@ -110,38 +110,38 @@ class TlvMapperTest { @Test fun `map CardStatus returns correct value`() { - val cardStatus: CardStatus = tlvMapper.map(TlvTag.Status) + val cardStatus: CardStatus = tlvMapper.decode(TlvTag.Status) assertThat(cardStatus) .isEqualTo(CardStatus.Loaded) } @Test fun `map ProductMask with raw value 5 returns correct value`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) - val productMask: ProductMask = localMapper.map(TlvTag.ProductMask) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) + val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) assertThat(productMask.contains(ProductMask.note) && productMask.contains(ProductMask.idCard)) .isTrue() } @Test fun `map ProductMask with raw value 1 returns correct value`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1)))) - val productMask: ProductMask = localMapper.map(TlvTag.ProductMask) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1)))) + val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) assertThat(productMask.contains(ProductMask.note)) .isTrue() } @Test fun `map Enum with unknown code throws ConversionException error`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.CurveId, "test".toByteArray()))) - assertThrows { - localMapper.map(TlvTag.CurveId) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.CurveId, "test".toByteArray()))) + assertThrows { + localMapper.decode(TlvTag.CurveId) } } @Test fun `map DateTime returns correct value`() { - val date: Date = cardDataMapper.map(TlvTag.ManufactureDateTime) + val date: Date = cardDataMapper.decode(TlvTag.ManufactureDateTime) val expected = Calendar.getInstance().apply { this.set(2019, 4, 2, 0, 0, 0) }.time assertThat(date.toString()) .isEqualTo(expected.toString()) @@ -149,43 +149,43 @@ class TlvMapperTest { @Test fun `map EllipticCurve returns correct value`() { - val ellipticCurve: EllipticCurve = tlvMapper.map(TlvTag.CurveId) + val ellipticCurve: EllipticCurve = tlvMapper.decode(TlvTag.CurveId) assertThat(ellipticCurve) .isEqualTo(EllipticCurve.Secp256k1) } @Test fun `map ByteArray returns correctly`() { - val cardPublicKey: ByteArray = tlvMapper.map(TlvTag.CardPublicKey) + val cardPublicKey: ByteArray = tlvMapper.decode(TlvTag.CardPublicKey) assertThat(cardPublicKey) .isInstanceOf(ByteArray::class.java) } @Test fun `map Int returns correct value`() { - val signedHashes: Int = tlvMapper.map(TlvTag.SignedHashes) + val signedHashes: Int = tlvMapper.decode(TlvTag.SignedHashes) assertThat(signedHashes) .isEqualTo(13) } @Test fun `map Int with wrong value throws ConversionException`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5)))) - assertThrows { - localMapper.map(TlvTag.SignedHashes) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5)))) + assertThrows { + localMapper.decode(TlvTag.SignedHashes) } } @Test fun `map UTF8 returns correct value`() { - val blockchainId: String = cardDataMapper.map(TlvTag.BlockchainId) + val blockchainId: String = cardDataMapper.decode(TlvTag.BlockchainId) assertThat(blockchainId) .isEqualTo("ETH") } @Test fun `map Hex returns correct value`() { - val cardId: String = tlvMapper.map(TlvTag.CardId) + val cardId: String = tlvMapper.decode(TlvTag.CardId) assertThat(cardId) .isEqualTo("cb22000000027374") } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt index c9fc162b6a..ab604ee506 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt @@ -1,6 +1,8 @@ package com.tangem.tangemtest import android.app.Application +import android.content.Context +import android.content.SharedPreferences import com.tangem.tangemtest._arch.structure.ILog import com.tangem.tangemtest._arch.structure.ItemLogger import com.tangem.tangemtest.commons.TangemLogger @@ -23,6 +25,10 @@ class AppTangemDemo : Application() { ILog.setLogger(ItemLogger()) } + fun sharedPreferences(name: String = "DevKitApp", mode: Int = Context.MODE_PRIVATE): SharedPreferences { + return getSharedPreferences(name, mode) + } + companion object { lateinit var appInstance: AppTangemDemo } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt index b39081b6b0..40998ca19b 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt @@ -3,17 +3,14 @@ package com.tangem.tangemtest import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tangemtest.extensions.create -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.android.synthetic.main.old_activity_main.* class Old_MainActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var cardId: String private lateinit var issuerData: ByteArray private lateinit var issuerDataSignature: ByteArray @@ -23,66 +20,50 @@ class Old_MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.old_activity_main) - cardManager = CardManager.init(this) + tangemSdk = TangemSdk.init(this) btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> + tangemSdk.scanCard { taskEvent -> when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { - tv_card_cid?.text = cardId - btn_create_wallet.isEnabled = true - } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - runOnUiThread { - tv_card_cid?.text = cardId - btn_sign.isEnabled = true - btn_read_issuer_data.isEnabled = true - btn_read_issuer_extra_data.isEnabled = true - btn_write_issuer_data.isEnabled = true - btn_purge_wallet.isEnabled = true - btn_create_wallet.isEnabled = true - } - } + is CompletionResult.Success -> { + // Handle returned card data + val card = taskEvent.data + cardId = card.cardId + runOnUiThread { + tv_card_cid?.text = cardId + btn_create_wallet.isEnabled = true + tv_card_cid?.text = cardId + btn_sign.isEnabled = true + btn_read_issuer_data.isEnabled = true + btn_read_issuer_extra_data.isEnabled = true + btn_write_issuer_data.isEnabled = true + btn_purge_wallet.isEnabled = true + btn_create_wallet.isEnabled = true + } } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion - } } } } btn_sign?.setOnClickListener { _ -> - cardManager.sign( + tangemSdk.sign( createSampleHashes(), cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } } } } btn_read_issuer_data?.setOnClickListener { _ -> - cardManager.readIssuerData(cardId) { + tangemSdk.readIssuerData(cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { btn_write_issuer_data.isEnabled = true tv_card_cid?.text = it.data.issuerData.contentToString() issuerData = it.data.issuerData @@ -92,27 +73,27 @@ class Old_MainActivity : AppCompatActivity() { } } btn_write_issuer_data?.setOnClickListener { _ -> - cardManager.writeIssuerData( + tangemSdk.writeIssuerData( cardId, issuerData, issuerDataSignature) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.cardId } } } } btn_read_issuer_extra_data?.setOnClickListener { _ -> - cardManager.readIssuerExtraData(cardId) { + tangemSdk.readIssuerExtraData(cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1 btn_write_issuer_data.isEnabled = true tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data." @@ -121,26 +102,26 @@ class Old_MainActivity : AppCompatActivity() { } } btn_purge_wallet?.setOnClickListener { _ -> - cardManager.purgeWallet( + tangemSdk.purgeWallet( cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.status.name } } } } btn_create_wallet?.setOnClickListener { _ -> - cardManager.createWallet( + tangemSdk.createWallet( cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.status.name btn_sign.isEnabled = true btn_read_issuer_data.isEnabled = true diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt index 9ffcba3103..7f3ba0639d 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt @@ -5,14 +5,11 @@ import android.view.View import android.widget.CompoundButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager -import com.tangem.commands.ReadUserDataResponse -import com.tangem.commands.WriteUserDataResponse -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.android.synthetic.main.activity_test_user_data.* import java.nio.charset.StandardCharsets @@ -21,7 +18,7 @@ import java.nio.charset.StandardCharsets */ class TestUserDataActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var writeOptions: WriteOptions override fun onCreate(savedInstanceState: Bundle?) { @@ -33,31 +30,15 @@ class TestUserDataActivity : AppCompatActivity() { } private fun init() { - cardManager = CardManager.init(this) + tangemSdk = TangemSdk.init(this) btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> + tangemSdk.scanCard { taskEvent -> when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { showReadWriteSection(true) } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion + is CompletionResult.Success -> { + // Handle returned card data + writeOptions.cardId = taskEvent.data.cardId + runOnUiThread { showReadWriteSection(true) } } } } @@ -66,7 +47,7 @@ class TestUserDataActivity : AppCompatActivity() { btn_write.setOnClickListener { if (writeOptions.cardId == null) return@setOnClickListener - cardManager.writeUserData( + tangemSdk.writeUserData( writeOptions.cardId!!, writeOptions.userData, writeOptions.userProtectedData, @@ -74,16 +55,9 @@ class TestUserDataActivity : AppCompatActivity() { writeOptions.userProtectedCounter ) { when (it) { - is TaskEvent.Completion -> handleError(tv_write_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? WriteUserDataResponse - if (data == null) { - tv_write_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_write_result?.text = "Success" - } + is CompletionResult.Failure -> handleError(tv_write_result, it.error) + is CompletionResult.Success -> { + runOnUiThread { tv_write_result?.text = "Success" } } } } @@ -92,28 +66,24 @@ class TestUserDataActivity : AppCompatActivity() { btn_read.setOnClickListener { if (writeOptions.cardId == null) return@setOnClickListener - cardManager.readUserData(writeOptions.cardId!!) { + tangemSdk.readUserData(writeOptions.cardId!!) { when (it) { - is TaskEvent.Completion -> handleError(tv_read_result, it.error) - is TaskEvent.Event -> { + is CompletionResult.Failure -> handleError(tv_write_result, it.error) + is CompletionResult.Success -> { runOnUiThread { - val data = it.data as? ReadUserDataResponse - if (data == null) { - tv_read_result.text = "Response doesn't match" - return@runOnUiThread - } + tv_read_result?.text = "Success" - writeOptions.userData = data.userData - writeOptions.userProtectedData = data.userProtectedData - writeOptions.userCounter = data.userCounter - writeOptions.userProtectedCounter = data.userProtectedCounter + writeOptions.userData = it.data.userData + writeOptions.userProtectedData = it.data.userProtectedData + writeOptions.userCounter = it.data.userCounter + writeOptions.userProtectedCounter = it.data.userProtectedCounter - tv_card_cid.text = data.cardId - tv_data.text = String(data.userData, StandardCharsets.US_ASCII) - tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) - tv_counter.text = data.userCounter.toString() - tv_protected_counter.text = data.userProtectedCounter.toString() + tv_card_cid.text = it.data.cardId + tv_data.text = String(it.data.userData, StandardCharsets.US_ASCII) + tv_protected_data.text = String(it.data.userProtectedData, StandardCharsets.US_ASCII) + tv_counter.text = it.data.userCounter.toString() + tv_protected_counter.text = it.data.userProtectedCounter.toString() } } @@ -122,11 +92,10 @@ class TestUserDataActivity : AppCompatActivity() { } } - private fun handleError(tv: TextView, error: TaskError?) { - val er = error ?: return - if (er is TaskError.UserCancelled) return + private fun handleError(tv: TextView, error: SessionError) { + if (error is SessionError.UserCancelled) return - runOnUiThread { tv.text = er::class.simpleName } + runOnUiThread { tv.text = error::class.simpleName } } private fun initWriteOptions() { @@ -174,7 +143,7 @@ class WriteOptions { } fun updatePin2(chbx: CompoundButton) { - val value = CardEnvironment.DEFAULT_PIN2 + val value = SessionEnvironment.DEFAULT_PIN2 pin2 = if (chbx.isChecked) value else null } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt index 98704005f5..309309c947 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt @@ -3,10 +3,6 @@ package com.tangem.tangemtest._arch.structure /** [REDACTED_AUTHOR] */ -interface DataHolder { - var viewModel: D -} - typealias Payload = MutableMap interface PayloadHolder { @@ -17,12 +13,4 @@ interface PayloadHolder { fun set(key: String, value: Any?) { payload[key] = value } -} - -interface ItemListHolder { - fun setItems(list: MutableList) - fun getItems(): MutableList - fun addItem(item: I) - fun removeItem(item: I) - fun clear() } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt index 3b40edcc77..5e517808f2 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt @@ -5,6 +5,10 @@ package com.tangem.tangemtest._arch.structure */ interface Id +class StringId(val value: String) : Id + +class StringResId(val value: Int) : Id + enum class Additional : Id { UNDEFINED, JSON_INCOMING, diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt index 71924302a3..76b43ce50a 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt @@ -5,11 +5,11 @@ import com.tangem.tangemtest._arch.structure.Id /** [REDACTED_AUTHOR] */ -fun List.findDataItem(id: Id): BaseItem? { - var foundItem: BaseItem? = null +fun List.findItem(id: Id): Item? { + var foundItem: Item? = null iterate { if (it.id == id) { - foundItem = it as? BaseItem + foundItem = it return@iterate } } @@ -19,8 +19,8 @@ fun List.findDataItem(id: Id): BaseItem? { fun List.iterate(func: (Item) -> Unit) { forEach { when (it) { - is BaseItem<*> -> func(it) - is Block -> it.itemList.iterate(func) + is BaseItem -> func(it) + is ItemGroup -> it.itemList.iterate(func) } } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt index 2cfbc69173..eaf3d7afa4 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt @@ -1,31 +1,39 @@ package com.tangem.tangemtest._arch.structure.abstraction -import com.tangem.tangemtest._arch.structure.DataHolder import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.Payload -import com.tangem.tangemtest._arch.structure.PayloadHolder /** [REDACTED_AUTHOR] */ -interface Item : PayloadHolder { +interface Item { val id: Id var parent: Item? -} + var viewModel: ItemViewModel -abstract class BaseItem( - override var viewModel: ItemViewModel -) : Item, DataHolder> { - override var parent: Item? = null - override val payload: Payload = mutableMapOf() + fun added(parent: Item) { + this.parent = parent + } - fun getData(): D? = viewModel.data + fun removed(parent: Item) { + this.parent = null + } - fun setData(value: D?) { + fun getData(): D? = viewModel.data as? D + + fun setData(value: Any?) { viewModel.data = value } - fun restoreDefaultData(){ + fun restoreDefaultData() { setData(viewModel.defaultData) } +} + +open class BaseItem( + override val id: Id, + override var viewModel: ItemViewModel +) : Item { + + override var parent: Item? = null + } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt similarity index 61% rename from tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt index 11086e6f93..61a43a0d1c 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt @@ -2,45 +2,47 @@ package com.tangem.tangemtest._arch.structure.abstraction import com.tangem.tangemtest._arch.structure.ILog import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.ItemListHolder /** [REDACTED_AUTHOR] */ -interface Block : Item { +interface ItemGroup : Item { val itemList: MutableList + + fun setItems(list: MutableList) + fun getItems(): MutableList + fun addItem(item: Item) + fun removeItem(item: Item) + fun clear() } -abstract class BaseBlock : Block { +open class SimpleItemGroup( + override val id: Id, + override var viewModel: ItemViewModel = BaseItemViewModel() +) : ItemGroup { + override var parent: Item? = null override val itemList: MutableList = mutableListOf() - override val payload: MutableMap = mutableMapOf() -} - -open class ListItemBlock( - override val id: Id -) : BaseBlock(), ItemListHolder { override fun setItems(list: MutableList) { ILog.d(this, "setItems into: $id, count: ${list.size}") + itemList.forEach { it.removed(this) } itemList.clear() - itemList.addAll(list) - itemList.forEach { it.parent = this } + list.forEach { addItem(it) } } - override fun getItems(): MutableList { - return itemList - } + override fun getItems(): MutableList = itemList override fun addItem(item: Item) { ILog.d(this, "addItem into: $id, who: ${item.id}") - item.parent = this itemList.add(item) + item.added(this) } override fun removeItem(item: Item) { ILog.d(this, "removeItem from: $id, which: ${item.id}") itemList.remove(item) + item.removed(this) } override fun clear() { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt new file mode 100644 index 0000000000..a542a3be4d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt @@ -0,0 +1,101 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder + + +/** +[REDACTED_AUTHOR] + */ +typealias ValueChanged = (V?) -> Unit +typealias SafeValueChanged = (V) -> Unit + +class KeyValue(val key: String, val value: Any) + +class ViewState( + isVisible: Boolean? = null, + bgColor: Int? = -1 +) { + + class State( + stateValue: T, + var onValueChanged: SafeValueChanged? = null + ) { + var value = stateValue + set(value) { + if (preventSameChanges && field == value) return + + field = value + onValueChanged?.invoke(value) + } + + internal var preventSameChanges = true + } + + var isVisibleState = State(isVisible) + var backgroundColor = State(bgColor) + var descriptionVisibility = State(0x00000008) + + internal fun preventSameChanges(isPrevented: Boolean) { + val states = listOf(isVisibleState, backgroundColor, descriptionVisibility) + states.forEach { it.preventSameChanges = isPrevented } + } +} + +interface ItemViewModel : PayloadHolder { + val viewState: ViewState + var data: Any? + var defaultData: Any? + var onDataUpdated: ValueChanged? + + fun updateDataByView(data: Any?) +} + +open class BaseItemViewModel( + value: Any? = null, + override val viewState: ViewState = ViewState() +) : ItemViewModel { + + override val payload: Payload = mutableMapOf() + + // Don't update it directly from a View. Use for it updateDataByView() + override var data: Any? = value + set(value) { + if (handleDataUpdates(value)) field = value + } + + // Data for restoring initial value + override var defaultData: Any? = value + set(value) { + field = value + data = value + } + + // Use it for handling data updates in View + override var onDataUpdated: ValueChanged? = null + + // When data updates directly it invokes onDataUpdated + // return true = data will update + // return false = data won't update + protected open fun handleDataUpdates(value: Any?): Boolean { + ILog.d(this, "handleDateUpdates: $value") + onDataUpdated?.invoke(value) + return true + } + + // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop + override fun updateDataByView(data: Any?) { + ILog.d(this, "data changed: $data") + val callback = onDataUpdated + onDataUpdated = null + this.data = data + onDataUpdated = callback + } +} + +class ListViewModel( + val itemList: List, + var selectedItem: Any?, + override val viewState: ViewState = ViewState() +) : BaseItemViewModel(selectedItem) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt deleted file mode 100644 index 86d1d1b345..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.tangem.tangemtest._arch.structure.abstraction - -import com.tangem.tangemtest._arch.structure.ILog -import com.tangem.tangemtest._arch.structure.Payload -import com.tangem.tangemtest._arch.structure.PayloadHolder - - -/** -[REDACTED_AUTHOR] - */ -typealias ValueChange = (V?) -> Unit -typealias SafeValueChange = (V) -> Unit - -interface ItemViewModel : PayloadHolder { - val viewState: ViewState - var data: D? - var defaultData: D? - var onDataUpdated: ValueChange? - - fun updateDataByView(data: D?) -} - -open class BaseItemViewModel : ItemViewModel { - - override val viewState: ViewState = ViewState() - override val payload: Payload = mutableMapOf() - - // Don't update it directly from a View. Use for it updateDataByView() - override var data: D? = null - set(value) { - if (handleDataUpdates(value)) field = value - } - - // Data for restoring initial value - override var defaultData: D? = null - set(value) { - field = value - data = value - } - - // Use it for handling data updates in View - override var onDataUpdated: ValueChange? = null - - // When data updates directly it invokes onDataUpdated - // return true = data will update - // return false = data won't update - protected open fun handleDataUpdates(value: D?): Boolean { - ILog.d(this, "handleDateUpdates: $value") - onDataUpdated?.invoke(value) - return true - } - - // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop - override fun updateDataByView(data: D?) { - ILog.d(this, "data changed: $data") - val callback = onDataUpdated - onDataUpdated = null - this.data = data - onDataUpdated = callback - } -} - -class ViewState { - var isHiddenField = false - - var descriptionVisibility: Int = 0x00000008 - set(value) { - field = value - onDescriptionVisibilityChanged?.invoke(value) - } - - var onDescriptionVisibilityChanged: SafeValueChange? = null -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt index 11ea5510c0..e402650802 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt @@ -1,19 +1,39 @@ package com.tangem.tangemtest._arch.structure.impl import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.* /** [REDACTED_AUTHOR] */ -class AnyItem(override val id: Id, value: Any? = null) : BaseItem(AnyViewModel(value)) -class TextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) -class EditTextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) -class NumberItem(override val id: Id, value: Number? = null) : BaseItem(NumberViewModel(value)) -class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem(BoolViewModel(value)) -class ListItem(override val id: Id, value: List, selectedValue: Any?) - : BaseItem(ListViewModel(ListValueWrapper(selectedValue, value)) -) +open class TypedItem(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewModel) { + open fun getTypedData(): D? = viewModel.data as? D +} -inline fun BaseItem.getData(): T? = viewModel.data \ No newline at end of file +class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { + constructor(id: Id, value: String? = null, viewState: ViewState = ViewState()) + : this(id, BaseItemViewModel(value, viewState)) +} + +class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { + constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState()) + : this(id, BaseItemViewModel(value, viewState)) +} + +class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { + constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState()) + : this(id, BaseItemViewModel(value, viewState)) +} + + +class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { + constructor(id: Id, value: String? = null, viewState: ViewState = ViewState()) + : this(id, BaseItemViewModel(value, viewState)) +} + + +class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem(id, viewModel) { + constructor(id: Id, list: List, selectedValue: Any?, viewState: ViewState = ViewState()) + : this(id, ListViewModel(list, selectedValue, viewState)) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt deleted file mode 100644 index f082577e70..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.tangemtest._arch.structure.impl - -import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel - -/** -[REDACTED_AUTHOR] - */ -open class TransitiveViewModel(value: D?) : BaseItemViewModel() { - init { - defaultData = value - } -} - -class AnyViewModel(value: Any? = null) : TransitiveViewModel(value) -class StringViewModel(value: String? = null) : TransitiveViewModel(value) -class NumberViewModel(value: Number? = null) : TransitiveViewModel(value) -class BoolViewModel(value: Boolean? = null) : TransitiveViewModel(value) -class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel(value) { - - override fun handleDataUpdates(value: ListValueWrapper?): Boolean { - val newValue = value ?: return false - if (newValue.selectedItem == data?.selectedItem) return false - - return super.handleDataUpdates(value) - } -} - -class KeyValue(val key: String, val value: Any) -class ListValueWrapper(var selectedItem: Any?, val itemList: List) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt new file mode 100644 index 0000000000..2ed1e1ab8f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt @@ -0,0 +1,12 @@ +package com.tangem.tangemtest._arch.widget + +import android.view.ViewGroup +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget + +/** +[REDACTED_AUTHOR] + */ +interface ItemWidgetBuilder { + fun build(item: BaseItem, parent: ViewGroup): ViewWidget? +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt new file mode 100644 index 0000000000..3d9a3493a5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt @@ -0,0 +1,36 @@ +package com.tangem.tangemtest._arch.widget + +import android.view.ViewGroup +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup +import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget +import com.tangem.tangemtest._arch.widget.impl.LinearGroupWidget +import com.tangem.tangemtest._arch.widget.impl.StubWidget + +/** +[REDACTED_AUTHOR] + */ +class WidgetBuilder( + private val itemBuilder: ItemWidgetBuilder +) { + + fun build(item: Item, parent: ViewGroup): ViewWidget? { + return when (item) { + is ItemGroup -> buildBlock(item, parent) + is BaseItem -> itemBuilder.build(item, parent) + else -> StubWidget(item.id, parent) + } + } + + private fun buildBlock(itemGroup: ItemGroup, parent: ViewGroup): ViewWidget { + return when (itemGroup) { + is ItemGroup -> { + val linearBlock = LinearGroupWidget(parent, itemGroup) + itemGroup.getItems().forEach { build(it, linearBlock.view as ViewGroup) } + linearBlock + } + else -> StubWidget(itemGroup.id, parent) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt new file mode 100644 index 0000000000..9b6c5f9d2c --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt @@ -0,0 +1,79 @@ +package com.tangem.tangemtest._arch.widget.abstraction + +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.ColorRes +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.StringId +import com.tangem.tangemtest._arch.structure.StringResId +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.abstraction.ViewState +import ru.dev.gbixahue.eu4d.lib.android._android.views.colorFrom +import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder + +/** +[REDACTED_AUTHOR] + */ +interface ViewWidget : LayoutHolder { + val view: View + var item: Item + + fun getName(): String + fun setBackgroundColor(@ColorRes colorId: Int?) +} + +abstract class BaseViewWidget( + parent: ViewGroup, + override var item: Item +) : ViewWidget { + + override val view: View = inflate(getLayoutId(), parent) + + private var defaultBackground: Drawable? = view.background + + init { + subscribeToViewStateChanges(item.viewModel.viewState) + initViewState(item.viewModel.viewState) + } + + protected open fun subscribeToViewStateChanges(viewState: ViewState) { + viewState.isVisibleState.onValueChanged = { state -> + state?.let { view.visibility = if (it) View.VISIBLE else View.GONE } + } + viewState.backgroundColor.onValueChanged = { setBackgroundColor(it) } + } + + protected open fun initViewState(viewState: ViewState) { + viewState.preventSameChanges(false) + viewState.isVisibleState.value = viewState.isVisibleState.value + if (viewState.backgroundColor.value != -1) setBackgroundColor(viewState.backgroundColor.value) + viewState.preventSameChanges(true) + } + + override fun getName(): String { + return when (val id = item.id) { + is StringId -> id.value + is StringResId -> view.resources.getString(id.value) + else -> view.resources.getString(R.string.unknown) + } + } + + override fun setBackgroundColor(colorId: Int?) { + val background = when { + colorId == null -> null + colorId == -1 -> defaultBackground + else -> ColorDrawable(view.colorFrom(colorId)) + } + view.background = background + } +} + +internal fun inflate(id: Int, parent: ViewGroup): View { + val layoutId = if (id <= 0) R.layout.w_empty else id + val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + parent.addView(inflatedView) + return inflatedView +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt new file mode 100644 index 0000000000..97c8d36a35 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt @@ -0,0 +1,18 @@ +package com.tangem.tangemtest._arch.widget.impl + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup +import com.tangem.tangemtest._arch.widget.abstraction.BaseViewWidget + +/** +[REDACTED_AUTHOR] + */ +class LinearGroupWidget( + parent: ViewGroup, + itemGroup: ItemGroup +) : BaseViewWidget(parent, itemGroup) { + + override fun getLayoutId(): Int = R.layout.w_personilize_block + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt new file mode 100644 index 0000000000..1b832e362a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest._arch.widget.impl + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel +import com.tangem.tangemtest._arch.widget.abstraction.BaseViewWidget + +/** +[REDACTED_AUTHOR] + */ +class StubWidget(id: Id, parent: ViewGroup) : BaseViewWidget(parent, BaseItem(id, BaseItemViewModel())) { + override fun getLayoutId(): Int = R.layout.w_empty +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt index 4e774f0be7..92e093d2c8 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt @@ -2,7 +2,6 @@ package com.tangem.tangemtest._main import android.content.res.Resources import android.os.Bundle -import android.util.Log import android.view.Menu import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -12,8 +11,9 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupActionBarWithNavController +import androidx.navigation.ui.setupWithNavController import com.tangem.tangemtest.R +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log /** [REDACTED_AUTHOR] @@ -34,8 +34,8 @@ class MainActivity : AppCompatActivity() { .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return val navController = host.navController - appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point)) - setupActionBarWithNavController(navController, appBarConfiguration) + appBarConfiguration = AppBarConfiguration(navController.graph) + toolbar.setupWithNavController(navController, appBarConfiguration) navController.addOnDestinationChangedListener { _, destination, _ -> val dest: String = try { @@ -43,7 +43,7 @@ class MainActivity : AppCompatActivity() { } catch (e: Resources.NotFoundException) { destination.id.toString() } - Log.d("NavigationActivity", "Navigated to $dest") + Log.d(this, "Navigated to $dest") } } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt index 17ce97d8f9..a50da93a68 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt @@ -2,6 +2,8 @@ package com.tangem.tangemtest._main import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.tangem.commands.CommandResponse +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log /** [REDACTED_AUTHOR] @@ -9,7 +11,16 @@ import androidx.lifecycle.ViewModel class MainViewModel : ViewModel() { val ldDescriptionSwitch = MutableLiveData(false) + var commandResponse: CommandResponse? = null + fun switchToggled(state: Boolean) { ldDescriptionSwitch.postValue(state) } + + fun changeResponseEvent(commandResponse: CommandResponse?) { + Log.d(this, "changeResponseEvent") + val response = commandResponse ?: return + + this.commandResponse = response + } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt index 313c4aaffe..ea17194439 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt @@ -1,14 +1,10 @@ package com.tangem.tangemtest._main.entryPoint import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -19,21 +15,19 @@ import com.tangem.tangemtest._main.MainViewModel import com.tangem.tangemtest.ucase.getDefaultNavigationOptions import com.tangem.tangemtest.ucase.resources.ActionType import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.ui.BaseFragment import kotlinx.android.synthetic.main.fg_entry_point.* /** [REDACTED_AUTHOR] */ -class ActionListFragment : Fragment() { +class ActionListFragment : BaseFragment() { - private val navController: NavController by lazy { findNavController() } private lateinit var rvActions: RecyclerView private val mainActivityVM: MainViewModel by activityViewModels() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fg_entry_point, container, false) - } + override fun getLayoutId(): Int = R.layout.fg_entry_point override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -48,7 +42,9 @@ class ActionListFragment : Fragment() { val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false) - rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) } + rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> + navigateTo(data, options = getDefaultNavigationOptions()) + } mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { vhDataWrapper.descriptionIsVisible = it TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition()) @@ -64,10 +60,6 @@ class ActionListFragment : Fragment() { adapter.notifyDataSetChanged() } - private fun navigate(destinationId: Int) { - navController.navigate(destinationId, null, getDefaultNavigationOptions()) - } - private fun getNavigateOptions(): MutableList { return mutableListOf( ActionType.Scan, diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt new file mode 100644 index 0000000000..f5f02c0ace --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt @@ -0,0 +1,9 @@ +package com.tangem.tangemtest.commons + +/** +[REDACTED_AUTHOR] + */ +interface Store { + fun save(config: M) + fun restore(): M +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt index 7a19cf981f..59ae0da2b9 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt @@ -3,6 +3,7 @@ package com.tangem.tangemtest.commons /** [REDACTED_AUTHOR] */ -fun performAction(a: A?, b: B?, action: (A, B) -> Unit) { +fun performAction(a: A?, b: B?, action: (A, B) -> Unit, onFail: (() -> Unit)? = null) { if (a != null && b != null) action(a, b) + else onFail?.invoke() } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt new file mode 100644 index 0000000000..ce21b9fae6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt @@ -0,0 +1,85 @@ +package com.tangem.tangemtest.commons.view + +import android.widget.TextView +import androidx.annotation.StringRes +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.StringId +import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +open class MultiActionView( + stateActionList: MutableList, + val child: V +) { + + interface State { + val id: Id + + fun getAction(): SimpleFunction + fun getResNameId(): Int + } + + var afterAction: SafeValueChanged? = null + + var state: Id = DefaultId.default + set(value) { + if (field == value) return + + field = value + Log.d(this, "state changed to: ${getKey(value)}") + btnState = stateHolder[getKey(value)] + } + + init { + child.setOnClickListener { + val state = btnState ?: return@setOnClickListener + + Log.d(this, "child handled OnClick for: ${getKey(state.id)}") + state.getAction().invoke() + afterAction?.invoke(state.id) + } + } + + protected var btnState: State? = null + set(value) { + if (value == null) return + + field = value + Log.d(this, "btnState changed to: ${getKey(value.id)}") + child.setText(value.getResNameId()) + } + + protected val stateHolder: MutableMap = stateActionList.associateBy { getKey(it.id) }.toMutableMap() + + fun performAction(id: Id) { + Log.d(this, "performAction ${getKey(id)}") + state = id + child.performClick() + } + + protected open fun getKey(id: Id): String { + return when (id) { + is StringId -> id.value + else -> stringOf(id) + } + } +} + +enum class DefaultId : Id { default } + +typealias SimpleFunction = () -> Unit + +class ViewAction( + override val id: Id, + @StringRes val name: Int, + private val action: SimpleFunction +) : MultiActionView.State { + + override fun getAction(): SimpleFunction = action + + override fun getResNameId(): Int = name +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/AndroidComponents.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/AndroidComponents.kt new file mode 100644 index 0000000000..3a9205574c --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/AndroidComponents.kt @@ -0,0 +1,22 @@ +package com.tangem.tangemtest.extensions + +import android.content.Intent +import androidx.activity.ComponentActivity +import androidx.fragment.app.Fragment + +/** +[REDACTED_AUTHOR] + */ +fun Fragment.shareText(text: String) { + requireActivity().shareText(text) +} + +fun ComponentActivity.shareText(text: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt index 285c8de4b6..e8358b8071 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt @@ -1,6 +1,7 @@ package com.tangem.tangemtest.ucase.domain.actions -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.PayloadHolder @@ -8,7 +9,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import com.tangem.tasks.TaskEvent /** [REDACTED_AUTHOR] @@ -17,7 +17,7 @@ import com.tangem.tasks.TaskEvent * and then processing the response. It also allows you to extract the main action as a lambda expression */ data class AttrForAction( - val cardManager: CardManager, + val tangemSdk: TangemSdk, val itemList: List, val payload: Payload, val consequence: ItemsChangeConsequence? @@ -31,16 +31,18 @@ interface Action { abstract class BaseAction : Action { protected open fun handleResult( payload: PayloadHolder, - taskEvent: TaskEvent<*>, + commandResult: CompletionResult<*>, modifier: AfterActionModification?, attrs: AttrForAction, callback: ActionCallback ) { val modifiedItems = mutableListOf() - modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item -> + modifier?.modify(payload, commandResult, attrs.itemList)?.forEach { item -> modifiedItems.add(item) attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) } } - callback(taskEvent, modifiedItems) + callback(commandResult, modifiedItems) } + + override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt index 2433302a4b..5dd9d80fc1 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt @@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.PayloadHolder -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback import com.tangem.tangemtest.ucase.variants.TlvId @@ -11,10 +11,10 @@ import com.tangem.tangemtest.ucase.variants.TlvId */ class DepersonalizeAction : BaseAction() { override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findDataItem(TlvId.CardId) ?: return + val item = attrs.itemList.findItem(TlvId.CardId) ?: return val cardId = item.viewModel.data as? String ?: return - attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) } + attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) } } override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt index 8ef5e4c0cc..612b728c24 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt @@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest._arch.structure.impl.EditTextItem import com.tangem.tangemtest._arch.structure.impl.NumberItem import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback @@ -10,10 +10,10 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey import com.tangem.tangemtest.ucase.tunnel.ActionView import com.tangem.tangemtest.ucase.tunnel.ItemError import com.tangem.tangemtest.ucase.variants.personalize.CardNumber -import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigConverter -import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.PersonalizeConfigToCardConfig -import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizeParams -import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter +import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigToCardConfig +import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizationParams +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf /** @@ -33,31 +33,31 @@ class PersonalizeAction : BaseAction() { return } - val issuer = DefaultPersonalizeParams.issuer() - val acquirer = DefaultPersonalizeParams.acquirer() - val manufacturer = DefaultPersonalizeParams.manufacturer() + val issuer = DefaultPersonalizationParams.issuer() + val acquirer = DefaultPersonalizationParams.acquirer() + val manufacturer = DefaultPersonalizationParams.manufacturer() - val personalizeConfig = PersonalizeConfigConverter().convert(itemList, PersonalizeConfig()) - val cardConfig = PersonalizeConfigToCardConfig().convert(personalizeConfig) + val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig()) + val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig) - attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) { + attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) { handleResult(payload, it, null, attrs, callback) } } private fun checkSeries(itemList: List): Boolean { - val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false - val data = seriesItem.getData() ?: return false + val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false + val data = seriesItem.getData() as? String ?: return false seriesItem.setData(data.toUpperCase()) return data.length == 2 || data.length == 4 } private fun checkNumber(itemList: List): Boolean { - val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false - val numberItem = itemList.findDataItem(CardNumber.Number) as? NumberItem ?: return false - val seriesData = seriesItem.getData() ?: return false - val numberData = stringOf(numberItem.getData()) + val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false + val numberItem = itemList.findItem(CardNumber.Number) as? NumberItem ?: return false + val seriesData = seriesItem.getData() as? String ?: return false + val numberData = stringOf(numberItem.getData() as? Number) return if (seriesData.length == 2 && numberData.length > 13) false else !(seriesData.length == 4 && numberData.length > 11) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt index 5264be9aed..213fea8b63 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt @@ -9,6 +9,6 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.Aft */ class ScanAction : BaseAction() { override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } + attrs.tangemSdk.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt index 18fef1d8c5..375c2ce9a7 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt @@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.PayloadHolder -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback import com.tangem.tangemtest.ucase.variants.TlvId import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf @@ -12,11 +12,11 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf */ class SignAction : BaseAction() { override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val dataForHashing = attrs.itemList.findDataItem(TlvId.TransactionOutHash) ?: return + val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return val hash = dataForHashing.getData() as? ByteArray ?: return - val cardId = attrs.itemList.findDataItem(TlvId.CardId)?.viewModel?.data ?: return + val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } + attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } } override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt deleted file mode 100644 index 000e9a53c5..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tangemtest.ucase.domain.paramsManager - -import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager -import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizeItemsManager -import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager -import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager -import com.tangem.tangemtest.ucase.resources.ActionType -import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder - -/** -[REDACTED_AUTHOR] - */ -class ParamsManagerFactory : BaseTypedHolder() { - - companion object { - fun createFactory(): ParamsManagerFactory { - return ParamsManagerFactory().apply { - register(ActionType.Scan, ScanItemsManager()) - register(ActionType.Sign, SignItemsManager()) - register(ActionType.Personalize, PersonalizeItemsManager()) - register(ActionType.Depersonalize, DepersonalizeItemsManager()) - } - } - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt index f97013d6f8..a43809684b 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt @@ -1,14 +1,14 @@ package com.tangem.tangemtest.ucase.domain.paramsManager -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import com.tangem.tasks.TaskEvent -typealias ActionResponse = TaskEvent<*> +typealias ActionResponse = CompletionResult<*> typealias AffectedList = List typealias AffectedItemsCallback = (AffectedList) -> Unit typealias ActionCallback = (ActionResponse, AffectedList) -> Unit @@ -19,12 +19,12 @@ typealias ActionCallback = (ActionResponse, AffectedList) -> Unit interface ItemsManager : PayloadHolder { fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null) + fun setItems(items: List) fun getItems(): List - fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) - fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? + fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) + fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) + fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? fun attachPayload(payload: Payload) - - val consequence: ItemsChangeConsequence? } interface PayloadKey { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt index c9016165ac..6ddbc6a916 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt @@ -1,39 +1,59 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.managers -import com.tangem.CardManager +import androidx.lifecycle.LifecycleObserver +import com.tangem.TangemSdk import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest.ucase.domain.actions.Action import com.tangem.tangemtest.ucase.domain.actions.AttrForAction import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback import com.tangem.tangemtest.ucase.domain.paramsManager.AffectedItemsCallback import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log /** [REDACTED_AUTHOR] */ -abstract class BaseItemsManager(protected val action: Action) : ItemsManager { +open class BaseItemsManager(protected val action: Action) : ItemsManager, LifecycleObserver { - protected val itemList: List by lazy { createItemsList() } - - override val consequence: ItemsChangeConsequence? = null + init { + Log.d(this, "new instance created") + } override val payload: MutableMap = mutableMapOf() + protected var changeConsequence: ItemsChangeConsequence? = null + protected val itemList: MutableList = mutableListOf() + override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) { if (itemList.isEmpty()) return - val foundItem = itemList.findDataItem(id) ?: return + val foundItem = itemList.findItem(id) ?: return foundItem.setData(value) applyChangesByAffectedItems(foundItem, callback) } - override fun getItems(): MutableList = itemList.toMutableList() + override fun setItems(items: List) { + itemList.clear() + itemList.addAll(items) + } - override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? = null + override fun getItems(): List = itemList + + override fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) { + this.changeConsequence = consequence + } + + override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) + } + + override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(tangemSdk)) + } override fun attachPayload(payload: Payload) { payload.forEach { this.payload[it.key] = it.value } @@ -41,11 +61,9 @@ abstract class BaseItemsManager(protected val action: Action) : ItemsManager { // Use it if current item needs to be affect any other items protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) { - consequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } + changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } } - protected fun getAttrsForAction(cardManager: CardManager) - : AttrForAction = AttrForAction(cardManager, itemList, payload, consequence) - - abstract fun createItemsList(): List + protected fun getAttrsForAction(tangemSdk: TangemSdk) + : AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence) } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt deleted file mode 100644 index 8267c86bb6..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.tangemtest.ucase.domain.paramsManager.managers - -import com.tangem.CardManager -import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.impl.EditTextItem -import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction -import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback -import com.tangem.tangemtest.ucase.variants.TlvId - -/** -[REDACTED_AUTHOR] - */ -class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) { - override fun createItemsList(): List { - return listOf(EditTextItem(TlvId.CardId, null)) - } - - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) - } - - override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { - return action.getActionByTag(this, id, getAttrsForAction(cardManager)) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt new file mode 100644 index 0000000000..1652cd2833 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt @@ -0,0 +1,35 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.OnLifecycleEvent +import com.tangem.TangemSdk +import com.tangem.tangemtest.commons.Store +import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig + +/** +[REDACTED_AUTHOR] + */ +class PersonalizationItemsManager( + private val store: Store +) : BaseItemsManager(PersonalizeAction()) { + + private val converter = PersonalizationConfigConverter() + + init { + val config = store.restore() + setItems(converter.convert(config)) + } + + override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + val config = converter.convert(itemList, PersonalizationConfig()) + store.save(config) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt deleted file mode 100644 index 7f40ac54f5..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tangemtest.ucase.domain.paramsManager.managers - -import com.tangem.CardManager -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction -import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback - -/** -[REDACTED_AUTHOR] - */ -class PersonalizeItemsManager : BaseItemsManager(PersonalizeAction()) { - override fun createItemsList(): List = listOf() - - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt deleted file mode 100644 index 1d9c973786..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tangemtest.ucase.domain.paramsManager.managers - -import com.tangem.CardManager -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest.ucase.domain.actions.ScanAction -import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback - -/** -[REDACTED_AUTHOR] - */ -class ScanItemsManager : BaseItemsManager(ScanAction()) { - override fun createItemsList(): List = listOf() - - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt deleted file mode 100644 index 7d5d77a912..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tangemtest.ucase.domain.paramsManager.managers - -import com.tangem.CardManager -import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.impl.EditTextItem -import com.tangem.tangemtest.ucase.domain.actions.SignAction -import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback -import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence -import com.tangem.tangemtest.ucase.variants.TlvId - -/** -[REDACTED_AUTHOR] - */ -class SignItemsManager : BaseItemsManager(SignAction()) { - - override val consequence: ItemsChangeConsequence? by lazy { SignScanConsequence() } - - override fun createItemsList(): List { - return listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.TransactionOutHash, "Data used for hashing") - ) - } - - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) - } - - override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { - return action.getActionByTag(this, id, getAttrsForAction(cardManager)) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SimpleManagers.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SimpleManagers.kt new file mode 100644 index 0000000000..c892b6761b --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SimpleManagers.kt @@ -0,0 +1,31 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction +import com.tangem.tangemtest.ucase.domain.actions.ScanAction +import com.tangem.tangemtest.ucase.domain.actions.SignAction +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class ScanItemsManager : BaseItemsManager(ScanAction()) + +class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) { + + init { + setItems(listOf(EditTextItem(TlvId.CardId, null))) + } +} + +class SignItemsManager : BaseItemsManager(SignAction()) { + + init { + setItemChangeConsequences(SignScanConsequence()) + setItems(listOf( + EditTextItem(TlvId.CardId, null), + EditTextItem(TlvId.TransactionOutHash, "Data used for hashing") + )) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt index 0b6cecbf64..48ab757ff0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt @@ -1,8 +1,8 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tasks.TaskEvent /** [REDACTED_AUTHOR] @@ -12,5 +12,5 @@ import com.tangem.tasks.TaskEvent * Returns a list of items that have been modified */ interface AfterActionModification { - fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List + fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt index b75c721424..f63694fcc7 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt @@ -2,40 +2,40 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction import com.tangem.commands.Card import com.tangem.commands.CardStatus +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey import com.tangem.tangemtest.ucase.tunnel.ActionView import com.tangem.tangemtest.ucase.tunnel.CardError import com.tangem.tangemtest.ucase.variants.TlvId -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskEvent + import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI /** [REDACTED_AUTHOR] */ class AfterScanModifier : AfterActionModification { - override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List { - val foundItem = itemList.findDataItem(TlvId.CardId) ?: return listOf() - val card = smartCast(taskEvent)?.card ?: return listOf() + override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List { + val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf() + val card = smartCast(commandResult) ?: return listOf() val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf() return if (isNotPersonalized(card)) { actionView.showSnackbar(CardError.NotPersonalized) - postUI { actionView.showActionFab(false) } + postUI { actionView.enableActionFab(false) } listOf() } else { payload.set(PayloadKey.card, card) foundItem.setData(card.cardId) - postUI { actionView.showActionFab(true) } + postUI { actionView.enableActionFab(true) } listOf(foundItem) } } - private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? { - return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent + private fun smartCast(commandResult: CompletionResult<*>): Card? { + return (commandResult as? CompletionResult.Success)?.data } private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt index 2957d43a45..730c570be6 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt @@ -6,7 +6,7 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.calculateSha512 import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.findDataItem +import com.tangem.tangemtest._arch.structure.abstraction.findItem import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey import com.tangem.tangemtest.ucase.tunnel.ActionView import com.tangem.tangemtest.ucase.variants.TlvId @@ -22,20 +22,20 @@ interface ItemsChangeConsequence { fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List? } -class SignScanConsequence: ItemsChangeConsequence { +class SignScanConsequence : ItemsChangeConsequence { override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List? { if (changedItem.id != TlvId.CardId) return null - val hashItem = itemList.findDataItem(TlvId.TransactionOutHash) ?: return null + val hashItem = itemList.findItem(TlvId.TransactionOutHash) ?: return null val affectedItems = mutableListOf(hashItem) val card = payload.remove(PayloadKey.card) as Card? if (card == null) { hashItem.restoreDefaultData() - postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.showActionFab(false) } + postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.enableActionFab(false) } } else { val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s" - val hashedData = when(card.curve) { + val hashedData = when (card.curve) { EllipticCurve.Secp256k1 -> dataForHashing.calculateSha256() EllipticCurve.Ed25519 -> dataForHashing.calculateSha512() else -> return null diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt similarity index 90% rename from tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt index 32c93a7ff8..d54bc08a5d 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt @@ -1,10 +1,7 @@ package com.tangem.tangemtest.ucase.domain.responses import com.google.gson.* -import com.tangem.commands.ProductMask -import com.tangem.commands.Settings -import com.tangem.commands.SettingsMask -import com.tangem.commands.SigningMethod +import com.tangem.commands.* import com.tangem.common.extensions.toHexString import java.lang.reflect.Type import java.text.DateFormat @@ -13,7 +10,7 @@ import java.util.* /** [REDACTED_AUTHOR] */ -class GsonInitializer { +class ResponseJsonConverter { val gson: Gson by lazy { init() } private fun init(): Gson { @@ -27,6 +24,8 @@ class GsonInitializer { builder.setPrettyPrinting() return builder.create() } + + fun convertResponse(response: CommandResponse?): String = gson.toJson(response) } class ByteTypeAdapter : JsonSerializer { @@ -57,7 +56,7 @@ class SigningMethodTypeAdapter : JsonSerializer { } } -class DateTypeAdapter: JsonSerializer { +class DateTypeAdapter : JsonSerializer { override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US")) return JsonPrimitive(formatter.format(src).toString()) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt index 90b908b742..86c7454365 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt @@ -1,9 +1,13 @@ package com.tangem.tangemtest.ucase.resources +import com.tangem.tangemtest.AppTangemDemo import com.tangem.tangemtest.R import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.StringId +import com.tangem.tangemtest._arch.structure.StringResId import com.tangem.tangemtest.ucase.resources.initializers.ActionResources -import com.tangem.tangemtest.ucase.resources.initializers.PersonalizeResources +import com.tangem.tangemtest.ucase.resources.initializers.PersonalizationResources +import com.tangem.tangemtest.ucase.resources.initializers.ResponseResources import com.tangem.tangemtest.ucase.resources.initializers.TlvResources import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder @@ -14,13 +18,31 @@ open class ResourceHolder : BaseTypedHolder() object MainResourceHolder : ResourceHolder() { init { - PersonalizeResources().init(this) + PersonalizationResources().init(this) ActionResources().init(this) TlvResources().init(this) + ResponseResources().init(this) + } + + override fun get(type: Id): Resources? { + return when(type) { + is StringId -> getResourcesFromName(type.value) + is StringResId -> Resources(type.value) + else -> super.get(type) + } + } + + private fun getResourcesFromName(resName: String): Resources? { + val context = AppTangemDemo.appInstance.applicationContext + val resNameId = context.resources.getIdentifier(resName, "string", context.packageName) + val resInfoId = context.resources.getIdentifier("info_$resName", "string", context.packageName) + if (resNameId == 0) return null + + return Resources(resNameId, if (resInfoId == 0) null else resInfoId) } inline fun safeGet(id: Id): Res { val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown) return result as Res } -} +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizationResources.kt similarity index 96% rename from tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizationResources.kt index 3812343e7d..a45bc55ed4 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizationResources.kt @@ -10,7 +10,7 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.common.TypedHolder /** [REDACTED_AUTHOR] */ -class PersonalizeResources() { +class PersonalizationResources() { fun init(holder: ResourceHolder) { initBlock(holder) initCardNumber(holder) @@ -22,7 +22,7 @@ class PersonalizeResources() { initProductMask(holder) initSettingsMask(holder) initSettingsMaskProtocolEnc(holder) - initSettingsMaskNde(holder) + initSettingsMaskNdef(holder) initPins(holder) } @@ -115,11 +115,13 @@ class PersonalizeResources() { holder.register(SettingsMaskProtocolEnc.AllowStaticEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption)) } - private fun initSettingsMaskNde(holder: TypedHolder) { + private fun initSettingsMaskNdef(holder: TypedHolder) { holder.register(SettingsMaskNdef.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef)) holder.register(SettingsMaskNdef.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef)) holder.register(SettingsMaskNdef.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef)) holder.register(SettingsMaskNdef.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar)) + holder.register(SettingsMaskNdef.AarCustom, Resources(R.string.pers_item_custom_aar_package_name, R.string.info_pers_item_custom_aar_package_name)) + holder.register(SettingsMaskNdef.Uri, Resources(R.string.pers_item_uri, R.string.info_pers_item_uri)) } private fun initPins(holder: TypedHolder) { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ResponseResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ResponseResources.kt new file mode 100644 index 0000000000..a9ad0a3d69 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ResponseResources.kt @@ -0,0 +1,53 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import com.tangem.tangemtest.ucase.variants.responses.CardDataId +import com.tangem.tangemtest.ucase.variants.responses.CardId + +/** +[REDACTED_AUTHOR] + */ +class ResponseResources { + fun init(holder: MainResourceHolder) { + initCard(holder) + initCardData(holder) + } + + private fun initCard(holder: MainResourceHolder) { + holder.register(CardId.cardId, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) + holder.register(CardId.manufacturerName, Resources(R.string.response_card_manufacturer_name, R.string.info_response_card_manufacturer_name)) + holder.register(CardId.status, Resources(R.string.response_card_status, R.string.info_response_card_status)) + holder.register(CardId.firmwareVersion, Resources(R.string.response_card_firmware_version, R.string.info_response_card_firmware_version)) + holder.register(CardId.cardPublicKey, Resources(R.string.response_card_public_key, R.string.info_response_card_public_key)) + holder.register(CardId.settingsMask, Resources(R.string.response_card_settings_mask, R.string.info_response_card_settings_mask)) + holder.register(CardId.cardData, Resources(R.string.response_card_card_data, R.string.info_response_card_card_data)) + holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key)) + holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve)) + holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures)) + holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.response_card_signing_method)) + holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2)) + holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key)) + holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures)) + holder.register(CardId.walletSignedHashes, Resources(R.string.response_card_wallet_signed_hashes, R.string.info_response_card_wallet_signed_hashes)) + holder.register(CardId.health, Resources(R.string.response_card_health, R.string.info_response_card_health)) + holder.register(CardId.isActivated, Resources(R.string.response_card_is_activated, R.string.info_response_card_is_activated)) + holder.register(CardId.activationSeed, Resources(R.string.response_card_activation_seed, R.string.info_response_card_activation_seed)) + holder.register(CardId.paymentFlowVersion, Resources(R.string.response_card_payment_flow_version, R.string.info_response_card_payment_flow_version)) + holder.register(CardId.userCounter, Resources(R.string.response_card_user_counter, R.string.info_response_card_user_counter)) + holder.register(CardId.userProtectedCounter, Resources(R.string.response_card_user_protected_counter, R.string.info_response_card_user_protected_counter)) + } + + private fun initCardData(holder: MainResourceHolder) { + holder.register(CardDataId.batchId, Resources(R.string.response_card_card_data_batch_id, R.string.info_response_card_card_data_batch_id)) + holder.register(CardDataId.manufactureDateTime, Resources(R.string.response_card_card_data_manufacture_date_time, R.string.info_response_card_card_data_manufacture_date_time)) + holder.register(CardDataId.issuerName, Resources(R.string.response_card_card_data_issuer_name, R.string.info_response_card_card_data_issuer_name)) + holder.register(CardDataId.blockchainName, Resources(R.string.response_card_card_data_blockchain_name, R.string.info_response_card_card_data_blockchain_name)) + holder.register(CardDataId.manufacturerSignature, Resources(R.string.response_card_card_data_manufacturer_signature, R.string.info_response_card_card_data_manufacturer_signature)) + holder.register(CardDataId.productMask, Resources(R.string.response_card_card_data_product_mask, R.string.info_response_card_card_data_product_mask)) + holder.register(CardDataId.tokenSymbol, Resources(R.string.response_card_card_data_token_symbol, R.string.info_response_card_card_data_token_symbol)) + holder.register(CardDataId.tokenContractAddress, Resources(R.string.response_card_card_data_token_contract_address, R.string.info_response_card_card_data_token_contract_address)) + holder.register(CardDataId.tokenDecimal, Resources(R.string.response_card_card_data_token_decimal, R.string.info_response_card_card_data_token_decimal)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt index 7b7cb530f7..8ae2a5370a 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt @@ -5,13 +5,13 @@ import com.tangem.tangemtest._arch.structure.Id /** [REDACTED_AUTHOR] */ -interface Errors: Id +interface Errors : Id enum class CardError : Errors { NotPersonalized, } -enum class ItemError: Errors { +enum class ItemError : Errors { BadSeries, BadCardNumber, } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt index ec84ed97ba..53ab0b4883 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt @@ -1,15 +1,16 @@ package com.tangem.tangemtest.ucase.tunnel +import com.google.android.material.snackbar.Snackbar import com.tangem.tangemtest._arch.structure.Id +interface SnackbarHolder { + fun showSnackbar(message: String, length: Int = Snackbar.LENGTH_SHORT) + fun showSnackbar(id: Int, length: Int = Snackbar.LENGTH_SHORT) +} + interface ViewScreen -interface SnackbarHolder { - fun showSnackbar(message: String) - fun showSnackbar(id: Int) - fun showSnackbar(id: Id) -} - interface ActionView : ViewScreen, SnackbarHolder { - fun showActionFab(show: Boolean) + fun enableActionFab(enable: Boolean) + fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)? = null) } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt new file mode 100644 index 0000000000..80e1840a9d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt @@ -0,0 +1,177 @@ +package com.tangem.tangemtest.ucase.ui + +import android.view.View +import androidx.annotation.UiThread +import androidx.lifecycle.* +import com.google.gson.Gson +import com.tangem.SessionError +import com.tangem.TangemSdk +import com.tangem.commands.Card +import com.tangem.commands.CommandResponse +import com.tangem.common.CompletionResult +import com.tangem.tangemtest._arch.SingleLiveEvent +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.abstraction.iterate +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.responses.ResponseJsonConverter +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.tunnel.ViewScreen +import com.tangem.tangemtest.ucase.variants.personalize.converter.ItemTypes +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = ActionViewModel(manager) as T +} + +class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { + + val seResponse = SingleLiveEvent() + val seResponseData = SingleLiveEvent() + val seResponseCardData = SingleLiveEvent() + + val ldItemList = MutableLiveData(itemsManager.getItems()) + val seError: MutableLiveData = SingleLiveEvent() + val seChangedItems: MutableLiveData> = SingleLiveEvent() + + private val notifier: Notifier = Notifier(this) + private lateinit var tangemSdk: TangemSdk + + fun setCardManager(tangemSdk: TangemSdk) { + this.tangemSdk = tangemSdk + } + + @Deprecated("Events must be send directly from the Widget") + fun userChangedItem(id: Id, value: Any?) { + itemChanged(id, value) + } + + private fun itemChanged(id: Id, value: Any?) { + itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) } + } + + //invokes Scan, Sign etc... + fun invokeMainAction() { + if (!::tangemSdk.isInitialized) { + Log.e(this, "TangemSdk isn't initialized") + return + } + itemsManager.invokeMainAction(tangemSdk) { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + + fun getItemAction(id: Id): (() -> Unit)? { + val itemFunction = itemsManager.getActionByTag(id, tangemSdk) ?: return null + + return { + itemFunction { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + } + + fun toggleDescriptionVisibility(state: Boolean) { + ldItemList.value?.iterate { + it.viewModel.viewState.descriptionVisibility.value = if (state) View.VISIBLE else View.GONE + } + } + + fun attachToPayload(payload: Payload) { + itemsManager.attachPayload(payload) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun viewOnDestroy() { + val keyList = mutableListOf() + itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) } + keyList.forEach { itemsManager.payload.remove(it) } + } + + fun showFields(type: ActionType) { + toggleFieldsVisibility(type, true) + } + + fun hideFields(type: ActionType) { + toggleFieldsVisibility(type, false) + } + + private fun toggleFieldsVisibility(type: ActionType, show: Boolean) { + val oftenUsed = getItemsForTogglingVisibilityState(type) + val hidden = getItemIdsWhichWontShows(type) + itemsManager.getItems().iterate { + if (!hidden.contains(it.id)) { + if (!oftenUsed.contains(it.id)) { + it.viewModel.viewState.isVisibleState.value = show + } + } + + } + } + + private fun getItemsForTogglingVisibilityState(type: ActionType): List { + return when (type) { + ActionType.Personalize -> ItemTypes().oftenUsedList + else -> emptyList() + } + } + + private fun getItemIdsWhichWontShows(type: ActionType): List { + return when (type) { + ActionType.Personalize -> ItemTypes().hiddenList + else -> emptyList() + } + } +} + +internal class Notifier(private val vm: ActionViewModel) { + + private var notShowedError: SessionError? = null + private val gson: Gson = ResponseJsonConverter().gson + + fun handleActionResult(result: CompletionResult<*>, list: List) { + if (list.isNotEmpty()) notifyItemsChanged(list) + handleCompletionResult(result) + } + + @UiThread + fun notifyItemsChanged(list: List) { + vm.seChangedItems.postValue(list) + } + + fun handleCompletionResult(result: CompletionResult<*>) { + val commandResponse = result as? CompletionResult ?: return + + when (commandResponse) { + is CompletionResult.Success -> handleData(commandResponse.data) + is CompletionResult.Failure -> handleError(commandResponse.error) + } + } + + private fun handleData(event: CommandResponse?) { + vm.seResponse.postValue(event) + when (event) { + is Card -> vm.seResponseCardData.postValue(event) + else -> vm.seResponseData.postValue(event) + } + } + + private fun handleError(error: SessionError) { + Log.d(this, "error = $error") + when (error) { + is SessionError.UserCancelled -> { + if (notShowedError == null) { + vm.seError.postValue("User canceled the action") + } else { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null + } + } + else -> notShowedError = error + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt index d44d2c998d..e9916b33f0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt @@ -4,147 +4,151 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView -import androidx.fragment.app.Fragment +import androidx.core.view.plusAssign import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import com.google.android.material.floatingactionbutton.FloatingActionButton -import com.google.android.material.snackbar.BaseTransientBottomBar -import com.google.android.material.snackbar.Snackbar -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.commands.Card import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tangemtest.R import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.BaseItem import com.tangem.tangemtest._main.MainViewModel import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager -import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey -import com.tangem.tangemtest.ucase.resources.ActionType import com.tangem.tangemtest.ucase.tunnel.ActionView import com.tangem.tangemtest.ucase.tunnel.CardError import com.tangem.tangemtest.ucase.ui.widgets.ParameterWidget +import ru.dev.gbixahue.eu4d.lib.android._android.views.enable import ru.dev.gbixahue.eu4d.lib.android.global.log.Log -import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder /** [REDACTED_AUTHOR] */ -abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView { +abstract class BaseCardActionFragment : BaseFragment(), ActionView { - protected val incomingParamsContainer: ViewGroup by lazy { - mainView.findViewById(R.id.ll_incoming_params_container) - } - protected lateinit var mainView: View - protected val actionFab: FloatingActionButton by lazy { mainView.findViewById(R.id.fab_action) } + protected lateinit var itemContainer: ViewGroup + protected lateinit var actionFab: FloatingActionButton - protected val paramsVM: ParamsViewModel by viewModels { ActionViewModelFactory(itemsManager) } - protected val widgetList = mutableListOf() + protected abstract val itemsManager: ItemsManager + protected val mainActivityVM by activityViewModels() + protected val actionVM: ActionViewModel by viewModels { ActionViewModelFactory(itemsManager) } + protected val UNDEFINED = -1 - private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(getAction())!! } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - Log.d(this, "onCreateView") - mainView = inflater.inflate(getLayoutId(), container, false) - return mainView - } + private val paramsWidgetList = mutableListOf() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Log.d(this, "onViewCreated") - viewLifecycleOwner.lifecycle.addObserver(paramsVM) - paramsVM.setCardManager(CardManager.init(requireActivity())) - paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) + bindViews() + viewLifecycleOwner.lifecycle.addObserver(actionVM) + actionVM.setCardManager(TangemSdk.init(requireActivity())) + actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) + initFab() - showActionFab(false) - createWidgets { subscribeToViewModelChanges() } + createWidgets { + widgetsWasCreated() + subscribeToViewModelChanges() + } } - private fun initFab() { - Log.d(this, "initFab") - actionFab.setOnClickListener { paramsVM.invokeMainAction() } + protected open fun widgetsWasCreated() {} + + protected open fun bindViews() { + itemContainer = mainView.findViewById(R.id.ll_container) + actionFab = mainView.findViewById(R.id.fab_action) } - private fun createWidgets(widgetCreatedCallback: () -> Unit) { + protected open fun initFab() { + enableActionFab(false) + actionFab.setOnClickListener { actionVM.invokeMainAction() } + } + + protected open fun createWidgets(widgetCreatedCallback: () -> Unit) { Log.d(this, "createWidgets") - paramsVM.ldParams.observe(viewLifecycleOwner, Observer { itemList -> + actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList -> itemList.forEach { param -> - val widget = ParameterWidget(inflateParamView(incomingParamsContainer), param) - widget.onValueChanged = { id, value -> paramsVM.userChangedItem(id, value) } - widget.onActionBtnClickListener = paramsVM.getItemAction(param.id) - widgetList.add(widget) + val widget = ParameterWidget(inflateParamView(itemContainer), param) + widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) } + widget.onActionBtnClickListener = actionVM.getItemAction(param.id) + paramsWidgetList.add(widget) } widgetCreatedCallback() }) } - private fun subscribeToViewModelChanges() { + protected open fun subscribeToViewModelChanges() { Log.d(this, "subscribeToViewModelChanges") listenResponse() + listenResponseData() + listenResponseCardData() listenError() listenChangedItems() listenDescriptionSwitchChanges() } - protected open fun listenResponse() { - val tvResponse by lazy { mainView.findViewById(R.id.tv_action_response_json) } - paramsVM.ldResponse.observe(viewLifecycleOwner, Observer { - Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") - tvResponse.text = it + private fun listenResponse() { + actionVM.seResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen response: $it") + mainActivityVM.changeResponseEvent(it) }) } - protected open fun listenError() { - paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) }) + protected open fun listenResponseData() { + actionVM.seResponseData.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen responseData: $it") + navigateTo(R.id.action_nav_card_action_to_response_screen) + }) } + protected open fun listenResponseCardData() { + actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen responseCardData: $it") + responseCardDataHandled(it) + }) + } + + protected open fun responseCardDataHandled(card: Card?) {} + + protected open fun listenError() { + actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) }) + } + + @Deprecated("Start to use itemViewModel") protected open fun listenChangedItems() { - paramsVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList -> + actionVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList -> itemList.forEach { item -> - Log.d(this, "item changed from VM - name: ${item.id}") - val dataItem = item as? BaseItem ?: return@Observer - Log.d(this, "item changed from VM - name: ${dataItem.id}, value:${dataItem.viewModel.data}") - widgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data) + Log.d(this, "item changed from VM - name: ${item.id}, value:${item.viewModel.data}") + paramsWidgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data) } }) } protected open fun listenDescriptionSwitchChanges() { - val mainActViewModel by activityViewModels() - mainActViewModel.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { isEnabled -> - widgetList.forEach { it.toggleDescriptionVisibility(isEnabled) } + mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { + actionVM.toggleDescriptionVisibility(it) }) } private fun inflateParamView(where: ViewGroup): ViewGroup { val inflater = LayoutInflater.from(where.context) val view = inflater.inflate(R.layout.w_card_incoming_param, where, false) - where.addView(view) + where.plusAssign(view) return view as ViewGroup } - override fun showActionFab(show: Boolean) { - if (show) actionFab.show() else actionFab.hide() + override fun enableActionFab(enable: Boolean) { + actionFab.enable(enable) } - override fun showSnackbar(id: Id) { -// MainResourceHolder.safeGet<>() - when (id) { - CardError.NotPersonalized -> showSnackbar(R.string.card_error_not_personalized) - else -> showSnackbar(requireContext().getString(R.string.unknown)) + override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) { + val resourceId = when (id) { + CardError.NotPersonalized -> R.string.card_error_not_personalized + else -> additionalHandler?.invoke(id) ?: UNDEFINED } - } - override fun showSnackbar(id: Int) { - showSnackbar(requireContext().getString(id)) + if (resourceId != UNDEFINED) showSnackbar(resourceId) } - - override fun showSnackbar(message: String) { - Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show() - } - - abstract fun getAction(): ActionType } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseFragment.kt new file mode 100644 index 0000000000..29dfeec6be --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseFragment.kt @@ -0,0 +1,38 @@ +package com.tangem.tangemtest.ucase.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.NavOptions +import androidx.navigation.fragment.NavHostFragment.findNavController +import com.google.android.material.snackbar.Snackbar +import com.tangem.tangemtest.ucase.getDefaultNavigationOptions +import com.tangem.tangemtest.ucase.tunnel.SnackbarHolder +import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseFragment : Fragment(), LayoutHolder, SnackbarHolder { + + protected lateinit var mainView: View + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + mainView = inflater.inflate(getLayoutId(), container, false) + return mainView + } + + protected fun navigateTo(navigationId: Int, bundle: Bundle? = null, options: NavOptions? = getDefaultNavigationOptions()) { + findNavController(this).navigate(navigationId, bundle, options) + } + + override fun showSnackbar(id: Int, length: Int) { + showSnackbar(requireContext().getString(id), length) + } + + override fun showSnackbar(message: String, length: Int) { + Snackbar.make(mainView, message, length).show() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt deleted file mode 100644 index 2df743786e..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.tangem.tangemtest.ucase.ui - -import androidx.annotation.UiThread -import androidx.lifecycle.* -import com.google.gson.Gson -import com.tangem.CardManager -import com.tangem.commands.Card -import com.tangem.tangemtest._arch.SingleLiveEvent -import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.Payload -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest.commons.performAction -import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager -import com.tangem.tangemtest.ucase.domain.responses.GsonInitializer -import com.tangem.tangemtest.ucase.tunnel.ViewScreen -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = ParamsViewModel(manager) as T -} - -class ParamsViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { - - val ldCard = MutableLiveData() - val ldIsVerified = MutableLiveData() - val ldResponse = MutableLiveData() - val ldReadResponse = MutableLiveData() - val ldParams = MutableLiveData(itemsManager.getItems()) - - val seError: MutableLiveData = SingleLiveEvent() - val seChangedItems: MutableLiveData> = SingleLiveEvent() - - private val notifier: Notifier = Notifier(this) - private lateinit var cardManager: CardManager - - fun setCardManager(cardManager: CardManager) { - this.cardManager = cardManager - } - - fun userChangedItem(id: Id, value: Any?) { - itemChanged(id, value) - } - - private fun itemChanged(id: Id, value: Any?) { - itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) } - } - - //invokes Scan, Sign etc... - fun invokeMainAction() { - performAction(itemsManager, cardManager) { paramsManager, cardManager -> - paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams -> - notifier.handleActionResult(response, listOfChangedParams) - } - } - } - - fun getItemAction(id: Id): (() -> Unit)? { - val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null - - return { - itemFunction { response, listOfChangedParams -> - notifier.handleActionResult(response, listOfChangedParams) - } - } - } - - fun attachToPayload(payload: Payload) { - itemsManager.attachPayload(payload) - } - - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun viewOnDestroy() { - val keyList = mutableListOf() - itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) } - keyList.forEach { itemsManager.payload.remove(it) } - } -} - -internal class Notifier(private val vm: ParamsViewModel) { - - private var notShowedError: TaskError? = null - private val gson: Gson = GsonInitializer().gson - - fun handleActionResult(response: TaskEvent<*>, list: List) { - if (list.isNotEmpty()) notifyItemsChanged(list) - handleResponse(response) - } - - @UiThread - fun notifyItemsChanged(list: List) { - vm.seChangedItems.postValue(list) - } - - fun handleResponse(response: TaskEvent<*>) { - val taskEvent = response as? TaskEvent<*> ?: return - - when (taskEvent) { - is TaskEvent.Completion -> handleCompletionEvent(taskEvent) - is TaskEvent.Event -> handleDataEvent(taskEvent.data) - } - } - - private fun handleDataEvent(event: Any?) { - when (event) { - is ScanEvent.OnReadEvent -> { - vm.ldCard.postValue(event.card) - vm.ldReadResponse.postValue(gson.toJson(event)) - } - is ScanEvent.OnVerifyEvent -> { - vm.ldIsVerified.postValue(true) - } - else -> vm.ldResponse.postValue(gson.toJson(event)) - } - } - - private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) { - if (taskEvent.error == null) { - Log.d(this, "error = null") - if (notShowedError != null) { - vm.seError.postValue("${notShowedError!!::class.simpleName}") - notShowedError = null - } - } else { - Log.d(this, "error = ${taskEvent.error}") - when (taskEvent.error) { - is TaskError.UserCancelled -> { - if (notShowedError == null) { - vm.seError.postValue("User canceled the action") - } else { - vm.seError.postValue("${notShowedError!!::class.simpleName}") - notShowedError = null - } - } - else -> notShowedError = taskEvent.error - } - } - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt index 15ba1cdcba..96037984ea 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt @@ -12,8 +12,8 @@ import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import com.tangem.tangemtest.R import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.BaseItem import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem import com.tangem.tangemtest.ucase.resources.ActionType import com.tangem.tangemtest.ucase.resources.MainResourceHolder import com.tangem.tangemtest.ucase.resources.Resources @@ -29,7 +29,7 @@ class ParameterWidget( ) { val id: Id = item.id - val dataItem: BaseItem = item as BaseItem + val typedItem = item as EditTextItem var onValueChanged: ((Id, Any?) -> Unit)? = null var onActionBtnClickListener: (() -> Unit)? = null @@ -47,11 +47,11 @@ class ParameterWidget( private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } private var actionBtnVisibilityState: Int = btnAction.visibility - private var value: Any? = dataItem.viewModel.data + private var value: Any? = typedItem.viewModel.data init { tilValue.hint = tilValue.context.getString(getResNameId()) - etValue.setText(stringOf(dataItem.viewModel.data)) + etValue.setText(stringOf(typedItem.viewModel.data)) etValue.addTextChangedListener(valueWatcher) btnAction.setOnClickListener { onActionBtnClickListener?.invoke() } btnAction.setText(getResNameId(ActionType.Scan)) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt index 6567e465b1..e2ae9cdbdd 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt @@ -1,7 +1,8 @@ package com.tangem.tangemtest.ucase.variants.depersonalize.ui import com.tangem.tangemtest.R -import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment /** @@ -9,8 +10,7 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment */ class DepersonalizeActionFragment : BaseCardActionFragment() { + override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() } + override fun getLayoutId(): Int = R.layout.fg_depersonalize - - override fun getAction(): ActionType = ActionType.Depersonalize - } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt index 21347404cf..6305b68975 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt @@ -6,11 +6,9 @@ import com.tangem.tangemtest._arch.structure.Id [REDACTED_AUTHOR] */ -interface PersonalizeId : Id +interface PersonalizationId : Id -interface BlockItem : PersonalizeId - -enum class BlockId : PersonalizeId { +enum class BlockId : PersonalizationId { CardNumber, Common, SigningMethod, @@ -24,13 +22,13 @@ enum class BlockId : PersonalizeId { Pins, } -enum class CardNumber : BlockItem { +enum class CardNumber : PersonalizationId { Series, Number, BatchId, } -enum class Common : BlockItem { +enum class Common : PersonalizationId { Curve, Blockchain, BlockchainCustom, @@ -38,7 +36,7 @@ enum class Common : BlockItem { CreateWallet, } -enum class SigningMethod : BlockItem { +enum class SigningMethod : PersonalizationId { SignTx, SignTxRaw, SignValidatedTx, @@ -48,7 +46,7 @@ enum class SigningMethod : BlockItem { SignExternal, } -enum class SignHashExProp : BlockItem { +enum class SignHashExProp : PersonalizationId { PinLessFloorLimit, CryptoExKey, RequireTerminalCertSig, @@ -56,26 +54,26 @@ enum class SignHashExProp : BlockItem { CheckPin3, } -enum class Denomination : BlockItem { +enum class Denomination : PersonalizationId { WriteOnPersonalize, Denomination, } -enum class Token : BlockItem { +enum class Token : PersonalizationId { ItsToken, Symbol, ContractAddress, Decimal } -enum class ProductMask : BlockItem { +enum class ProductMask : PersonalizationId { Note, Tag, IdCard, IdIssuerCard } -enum class SettingsMask : BlockItem { +enum class SettingsMask : PersonalizationId { IsReusable, NeedActivation, ForbidPurge, @@ -94,20 +92,21 @@ enum class SettingsMask : BlockItem { RestrictOverwriteExtraIssuerData, } -enum class SettingsMaskProtocolEnc : BlockItem { +enum class SettingsMaskProtocolEnc : PersonalizationId { AllowUnencrypted, AllowStaticEncryption } -enum class SettingsMaskNdef : BlockItem { +enum class SettingsMaskNdef : PersonalizationId { UseNdef, DynamicNdef, DisablePrecomputedNdef, Aar, - AarCustom + AarCustom, + Uri, } -enum class Pins : BlockItem { +enum class Pins : PersonalizationId { Pin, Pin2, Pin3, diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/PersonalizationConfigStore.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/PersonalizationConfigStore.kt new file mode 100644 index 0000000000..77a68278f8 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/PersonalizationConfigStore.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.variants.personalize + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import com.google.gson.Gson +import com.tangem.tangemtest.AppTangemDemo +import com.tangem.tangemtest.commons.Store +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig + +class PersonalizationConfigStore(context: Context) : Store { + + private val key = "personalization_config" + + private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences() + private val gson: Gson = Gson() + + override fun save(config: PersonalizationConfig) { + sp.edit(true) { putString(key, gson.toJson(config)) } + } + + override fun restore(): PersonalizationConfig { + val json = sp.getString(key, gson.toJson(PersonalizationConfig())) + return gson.fromJson(json, PersonalizationConfig::class.java) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt index 3e674edc9b..37364a0f8a 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt @@ -2,18 +2,18 @@ package com.tangem.tangemtest.ucase.variants.personalize.converter import com.tangem.commands.EllipticCurve import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.impl.KeyValue +import com.tangem.tangemtest._arch.structure.abstraction.KeyValue import com.tangem.tangemtest.ucase.variants.personalize.* -import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder /** [REDACTED_AUTHOR] */ -class PersonalizeConfigValuesHolder : BaseTypedHolder() { +class ConfigValuesHolder : BaseTypedHolder() { // All registered association values can't be objects - fun init(default: PersonalizeConfig) { + fun init(default: PersonalizationConfig) { register(CardNumber.Series, Value(default.series)) register(CardNumber.Number, Value(default.startNumber)) register(CardNumber.BatchId, Value(default.batchId)) @@ -67,6 +67,7 @@ class PersonalizeConfigValuesHolder : BaseTypedHolder() { register(SettingsMaskNdef.DisablePrecomputedNdef, Value(default.disablePrecomputedNDEF)) register(SettingsMaskNdef.Aar, Value(default.aar, Helper.aarList())) register(SettingsMaskNdef.AarCustom, Value(default.aar)) + register(SettingsMaskNdef.Uri, Value(default.uri)) register(Pins.Pin, Value(default.PIN)) register(Pins.Pin2, Value(default.PIN2)) register(Pins.Pin3, Value(default.PIN3)) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ItemTypes.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ItemTypes.kt new file mode 100644 index 0000000000..b1013de610 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ItemTypes.kt @@ -0,0 +1,117 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.variants.personalize.* + +class ItemTypes { + + val blockIdList = mutableListOf( + BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination, + BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc, + BlockId.SettingsMaskNdef, BlockId.Pins + ) + + val listItemList = mutableListOf(Common.Curve, Common.Blockchain, SettingsMaskNdef.Aar, Pins.PauseBeforePin2) + + val boolList = mutableListOf( + Common.CreateWallet, SigningMethod.SignTx, SigningMethod.SignTxRaw, SigningMethod.SignValidatedTx, + SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer, + SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig, + SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken, + ProductMask.Note, ProductMask.Tag, ProductMask.IdCard, ProductMask.IdIssuerCard, SettingsMask.IsReusable, SettingsMask.NeedActivation, + SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu, + SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin, + SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay, + SettingsMask.SkipSecurityDelayIfValidated, SettingsMask.SkipPin2CvcIfValidated, + SettingsMask.SkipSecurityDelayOnLinkedTerminal, SettingsMask.RestrictOverwriteExtraIssuerData, + SettingsMaskProtocolEnc.AllowUnencrypted, SettingsMaskProtocolEnc.AllowStaticEncryption, + SettingsMaskNdef.UseNdef, SettingsMaskNdef.DynamicNdef, SettingsMaskNdef.DisablePrecomputedNdef + ) + + val editTextList = mutableListOf( + CardNumber.Series, CardNumber.BatchId, Common.BlockchainCustom, SignHashExProp.CryptoExKey, Token.Symbol, + Token.ContractAddress, Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, SettingsMaskNdef.AarCustom, SettingsMaskNdef.Uri + ) + + val numberList = mutableListOf( + CardNumber.Number, Common.MaxSignatures, SignHashExProp.PinLessFloorLimit, Denomination.Denomination, Token.Decimal + ) + + val hiddenList = mutableListOf( + CardNumber.Series, CardNumber.BatchId, Pins.Pin3, SigningMethod.SignExternal, + SignHashExProp.CryptoExKey, SignHashExProp.CheckPin3, SettingsMask.OneApdu, + SettingsMask.UseBlock, SettingsMask.ProtectIssuerDataAgainstReplay, + SignHashExProp.RequireTerminalCertSig, SignHashExProp.RequireTerminalTxSig + ) + + val oftenUsedList = listOf( +// BlockId.CardNumber, + BlockId.Common, +// BlockId.SigningMethod, +// BlockId.SignHashExProp, +// BlockId.Denomination, +// BlockId.Token, + BlockId.ProdMask, +// BlockId.SettingsMask, +// BlockId.SettingsMaskProtocolEnc, + BlockId.SettingsMaskNdef, +// BlockId.Pins, + Common.Curve, + Common.Blockchain, + Common.BlockchainCustom, +// Common.MaxSignatures, + Common.CreateWallet, +// SigningMethod.SignTx, +// SigningMethod.SignTxRaw, +// SigningMethod.SignValidatedTx, +// SigningMethod.SignValidatedTxRaw, +// SigningMethod.SignValidatedTxIssuer, +// SigningMethod.SignValidatedTxRawIssuer, +// SigningMethod.SignExternal, +// SignHashExProp.PinLessFloorLimit, +// SignHashExProp.CryptoExKey, +// SignHashExProp.RequireTerminalCertSig, +// SignHashExProp.RequireTerminalTxSig, +// SignHashExProp.CheckPin3, +// Denomination.WriteOnPersonalize, +// Denomination.Denomination, +// Token.ItsToken, +// Token.Symbol, +// Token.ContractAddress, +// Token.Decimal, + ProductMask.Note, + ProductMask.Tag, + ProductMask.IdCard, + ProductMask.IdIssuerCard, +// SettingsMask.IsReusable, +// SettingsMask.NeedActivation, +// SettingsMask.ForbidPurge, +// SettingsMask.AllowSelectBlockchain, +// SettingsMask.UseBlock, +// SettingsMask.OneApdu, +// SettingsMask.UseCvc, +// SettingsMask.AllowSwapPin, +// SettingsMask.AllowSwapPin2, +// SettingsMask.ForbidDefaultPin, +// SettingsMask.SmartSecurityDelay, +// SettingsMask.ProtectIssuerDataAgainstReplay, +// SettingsMask.SkipSecurityDelayIfValidated, +// SettingsMask.SkipPin2CvcIfValidated, +// SettingsMask.SkipSecurityDelayOnLinkedTerminal, +// SettingsMask.RestrictOverwriteExtraIssuerData, +// SettingsMaskProtocolEnc.AllowUnencrypted, +// SettingsMaskProtocolEnc.AllowStaticEncryption, +// SettingsMaskNdef.UseNdef, +// SettingsMaskNdef.DynamicNdef, +// SettingsMaskNdef.DisablePrecomputedNdef, + SettingsMaskNdef.Aar, + SettingsMaskNdef.AarCustom, + SettingsMaskNdef.Uri, +// Pins.Pin, +// Pins.Pin2, +// Pins.Pin3, +// Pins.Cvc, + Pins.PauseBeforePin2 + ) + +} diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt new file mode 100644 index 0000000000..d450ab54d9 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt @@ -0,0 +1,308 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.tangemtest._arch.structure.Additional +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.* +import com.tangem.tangemtest._arch.structure.impl.* +import com.tangem.tangemtest.ucase.variants.personalize.* +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class PersonalizationConfigConverter : ModelConverter { + + private val toModel: ItemsToModel = ItemsToPersonalizationConfig() + private val toItems: ModelToItems = PersonalizationConfigToItems() + + override fun convert(from: List, default: PersonalizationConfig): PersonalizationConfig { + return toModel.convert(from, default) + } + + override fun convert(from: PersonalizationConfig): List { + return toItems.convert(from) + } +} + +class ItemsToPersonalizationConfig : ItemsToModel { + protected val valuesHolder = ConfigValuesHolder() + + override fun convert(from: List, default: PersonalizationConfig): PersonalizationConfig { + valuesHolder.init(default) + mapListItems(from) + return createModel() + } + + private fun mapListItems(itemList: List) { + itemList.iterate { item -> mapItemToHolder(item) } + } + + private fun mapItemToHolder(item: Item) { + when (item) { + is ItemGroup -> mapListItems(item.itemList) + is BaseItem -> { + val defValue = valuesHolder.get(item.id) ?: return + defValue.set(item.viewModel.data) + } + } + } + + private fun createModel(): PersonalizationConfig { + val export = PersonalizationConfig() + export.series = getTyped(CardNumber.Series) + export.startNumber = getTyped(CardNumber.Number) + export.curveID = getTyped(Common.Curve) + export.blockchain = getTyped(Common.Blockchain) + export.blockchainCustom = getTyped(Common.BlockchainCustom) + export.MaxSignatures = getTyped(Common.MaxSignatures) + export.createWallet = getTyped(Common.CreateWallet) + export.SigningMethod0 = getTyped(SigningMethod.SignTx) + export.SigningMethod1 = getTyped(SigningMethod.SignTxRaw) + export.SigningMethod2 = getTyped(SigningMethod.SignValidatedTx) + export.SigningMethod3 = getTyped(SigningMethod.SignValidatedTxRaw) + export.SigningMethod4 = getTyped(SigningMethod.SignValidatedTxIssuer) + export.SigningMethod5 = getTyped(SigningMethod.SignValidatedTxRawIssuer) + export.SigningMethod6 = getTyped(SigningMethod.SignExternal) + export.pinLessFloorLimit = getTyped(SignHashExProp.PinLessFloorLimit) + export.hexCrExKey = getTyped(SignHashExProp.CryptoExKey) + export.requireTerminalCertSignature = getTyped(SignHashExProp.RequireTerminalCertSig) + export.requireTerminalTxSignature = getTyped(SignHashExProp.RequireTerminalTxSig) + export.checkPIN3onCard = getTyped(SignHashExProp.CheckPin3) + export.writeOnPersonalization = getTyped(Denomination.WriteOnPersonalize) + export.denomination = getTyped(Denomination.Denomination) + export.itsToken = getTyped(Token.ItsToken) + export.symbol = getTyped(Token.Symbol) + export.contractAddress = getTyped(Token.ContractAddress) + export.decimal = getTyped(Token.Decimal) + export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) } + export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) } + export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.IdCard) } + export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMask.IdIssuerCard) } + export.isReusable = getTyped(SettingsMask.IsReusable) + export.useActivation = getTyped(SettingsMask.NeedActivation) + export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge) + export.allowSelectBlockchain = getTyped(SettingsMask.AllowSelectBlockchain) + export.useBlock = getTyped(SettingsMask.UseBlock) + export.oneApdu = getTyped(SettingsMask.OneApdu) + export.useCVC = getTyped(SettingsMask.UseCvc) + export.allowSwapPIN = getTyped(SettingsMask.AllowSwapPin) + export.allowSwapPIN2 = getTyped(SettingsMask.AllowSwapPin2) + export.forbidDefaultPIN = getTyped(SettingsMask.ForbidDefaultPin) + export.smartSecurityDelay = getTyped(SettingsMask.SmartSecurityDelay) + export.protectIssuerDataAgainstReplay = getTyped(SettingsMask.ProtectIssuerDataAgainstReplay) + export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMask.SkipSecurityDelayIfValidated) + export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMask.SkipPin2CvcIfValidated) + export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMask.SkipSecurityDelayOnLinkedTerminal) + export.restrictOverwriteIssuerDataEx = getTyped(SettingsMask.RestrictOverwriteExtraIssuerData) + export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEnc.AllowUnencrypted) + export.protocolAllowStaticEncryption = getTyped(SettingsMaskProtocolEnc.AllowStaticEncryption) + export.useNDEF = getTyped(SettingsMaskNdef.UseNdef) + export.useDynamicNDEF = getTyped(SettingsMaskNdef.DynamicNdef) + export.disablePrecomputedNDEF = getTyped(SettingsMaskNdef.DisablePrecomputedNdef) + export.aar = getTyped(SettingsMaskNdef.Aar) + export.aarCustom = getTyped(SettingsMaskNdef.AarCustom) + export.uri = getTyped(SettingsMaskNdef.Uri) + export.PIN = getTyped(Pins.Pin) + export.PIN2 = getTyped(Pins.Pin2) + export.PIN3 = getTyped(Pins.Pin3) + export.CVC = getTyped(Pins.Cvc) + export.pauseBeforePIN2 = getTyped(Pins.PauseBeforePin2) + return export + } + + private inline fun getTyped(id: Id): Type { + return getTypedBy(valuesHolder, id)!! + } + + private inline fun getTypedBy(holder: ConfigValuesHolder, id: Id): Type? { + Log.d(this, "getTyped for id: $id") + var typedValue = holder.get(id)?.get() + + typedValue = when (typedValue) { + is ListViewModel -> typedValue.selectedItem as Type + else -> typedValue as? Type ?: null + } + return typedValue + } +} + +class PersonalizationConfigToItems : ModelToItems { + private val valuesHolder = ConfigValuesHolder() + private val itemTypes = ItemTypes() + + override fun convert(from: PersonalizationConfig): List { + valuesHolder.init(from) + val blocList = mutableListOf() + blocList.add(cardNumber()) + blocList.add(common()) + blocList.add(signingMethod()) + blocList.add(signHashExProperties()) + blocList.add(denomination()) + blocList.add(token()) + blocList.add(productMask()) + blocList.add(settingsMask()) + blocList.add(settingsMaskProtocolEnc()) + blocList.add(settingsMaskNdef()) + blocList.add(pins()) + blocList.iterate { + if (itemTypes.hiddenList.contains(it.id)) { + it.viewModel.viewState.isVisibleState.value = false + } + } + return blocList + } + + private fun cardNumber(): ItemGroup { + val block = createGroup(BlockId.CardNumber) + mutableListOf( + CardNumber.Series, + CardNumber.Number, + CardNumber.BatchId + ).forEach { createItem(block, it as Id) } + return block + } + + private fun common(): ItemGroup { + val block = createGroup(BlockId.Common) + mutableListOf( + Common.Blockchain, + Common.BlockchainCustom, + Common.Curve, + Common.MaxSignatures, + Common.CreateWallet, + Pins.PauseBeforePin2 + ).forEach { createItem(block, it as Id) } + return block + } + + private fun signingMethod(): ItemGroup { + val block = createGroup(BlockId.SigningMethod) + mutableListOf( + SigningMethod.SignTx, + SigningMethod.SignTxRaw, + SigningMethod.SignValidatedTx, + SigningMethod.SignValidatedTxRaw, + SigningMethod.SignValidatedTxIssuer, + SigningMethod.SignValidatedTxRawIssuer, + SigningMethod.SignExternal + ).forEach { createItem(block, it) } + return block + } + + private fun signHashExProperties(): ItemGroup { + val block = createGroup(BlockId.SignHashExProp) + mutableListOf( + SignHashExProp.PinLessFloorLimit, + SignHashExProp.CryptoExKey, + SignHashExProp.RequireTerminalCertSig, + SignHashExProp.RequireTerminalTxSig, + SignHashExProp.CheckPin3 + ).forEach { createItem(block, it) } + return block + } + + private fun denomination(): ItemGroup { + val block = createGroup(BlockId.Denomination) + mutableListOf( + Denomination.WriteOnPersonalize, + Denomination.Denomination + ).forEach { createItem(block, it) } + return block + } + + private fun token(): ItemGroup { + val block = createGroup(BlockId.Token) + mutableListOf( + Token.ItsToken, + Token.Symbol, + Token.ContractAddress, + Token.Decimal + ).forEach { createItem(block, it) } + return block + } + + private fun productMask(): ItemGroup { + val block = createGroup(BlockId.ProdMask) + mutableListOf( + ProductMask.Note, + ProductMask.Tag, + ProductMask.IdCard, + ProductMask.IdIssuerCard + ).forEach { createItem(block, it) } + return block + } + + private fun settingsMask(): ItemGroup { + val block = createGroup(BlockId.SettingsMask) + mutableListOf( + SettingsMask.IsReusable, + SettingsMask.NeedActivation, + SettingsMask.ForbidPurge, + SettingsMask.AllowSelectBlockchain, + SettingsMask.UseBlock, + SettingsMask.OneApdu, + SettingsMask.UseCvc, + SettingsMask.AllowSwapPin, + SettingsMask.AllowSwapPin2, + SettingsMask.ForbidDefaultPin, + SettingsMask.SmartSecurityDelay, + SettingsMask.ProtectIssuerDataAgainstReplay, + SettingsMask.SkipSecurityDelayIfValidated, + SettingsMask.SkipPin2CvcIfValidated, + SettingsMask.SkipSecurityDelayOnLinkedTerminal, + SettingsMask.RestrictOverwriteExtraIssuerData + ).forEach { createItem(block, it) } + return block + } + + private fun settingsMaskProtocolEnc(): ItemGroup { + val block = createGroup(BlockId.SettingsMaskProtocolEnc) + mutableListOf( + SettingsMaskProtocolEnc.AllowUnencrypted, + SettingsMaskProtocolEnc.AllowStaticEncryption + ).forEach { createItem(block, it) } + return block + } + + private fun settingsMaskNdef(): ItemGroup { + val block = createGroup(BlockId.SettingsMaskNdef) + mutableListOf( + SettingsMaskNdef.UseNdef, + SettingsMaskNdef.DynamicNdef, + SettingsMaskNdef.DisablePrecomputedNdef, + SettingsMaskNdef.Aar, + SettingsMaskNdef.AarCustom, + SettingsMaskNdef.Uri + ).forEach { createItem(block, it) } + return block + } + + private fun pins(): ItemGroup { + val block = createGroup(BlockId.Pins) + mutableListOf( + Pins.Pin, + Pins.Pin2, + Pins.Pin3, + Pins.Cvc + ).forEach { createItem(block, it) } + return block + } + + private fun createGroup(id: Id): ItemGroup { + return SimpleItemGroup(id).apply { addItem(TextItem(id)) } + } + + private fun createItem(itemGroup: ItemGroup, id: Id) { + val holder = valuesHolder.get(id) ?: return + val item = when { + itemTypes.blockIdList.contains(id) -> TextItem(id, holder.get() as? String) + itemTypes.listItemList.contains(id) -> SpinnerItem(id, holder.list as List, holder.get()) + itemTypes.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean) + itemTypes.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String) + itemTypes.numberList.contains(id) -> NumberItem(id, holder.get() as? Number) + else -> SimpleItemGroup(Additional.UNDEFINED) + } + itemGroup.addItem(item) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigToCardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigToCardConfig.kt new file mode 100644 index 0000000000..fa79df96e0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizationConfigToCardConfig.kt @@ -0,0 +1,113 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.commands.CardData +import com.tangem.commands.EllipticCurve +import com.tangem.commands.ProductMaskBuilder +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.NdefRecord +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig +import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter +import java.util.* + +class PersonalizationConfigToCardConfig : Converter { + + override fun convert(from: PersonalizationConfig): CardConfig { + val signingMethod = com.tangem.commands.SigningMethod.build( + signHash = from.SigningMethod0, + signRaw = from.SigningMethod1, + signHashValidatedByIssuer = from.SigningMethod2, + signRawValidatedByIssuer = from.SigningMethod3, + signHashValidatedByIssuerAndWriteIssuerData = from.SigningMethod4, + signRawValidatedByIssuerAndWriteIssuerData = from.SigningMethod5, + signPos = from.SigningMethod6 + ) + + val isNote = from.cardData.product_note + val isTag = from.cardData.product_tag + val isIdCard = from.cardData.product_id_card + val isIdIssuer = from.cardData.product_id_issuer + + val productMaskBuilder = ProductMaskBuilder() + if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note) + if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag) + if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard) + if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.ProductMask.idIssuer) + val productMask = productMaskBuilder.build() + + var tokenSymbol: String? = null + var tokenContractAddress: String? = null + var tokenDecimal: Int? = null + if (from.itsToken) { + tokenSymbol = from.symbol + tokenContractAddress = from.contractAddress + tokenDecimal = from.decimal.toInt() + } + + val blockchain = if (from.blockchain.isNotEmpty()) from.blockchain else from.blockchainCustom + val cardData = CardData( + blockchainName = blockchain, + batchId = from.batchId, + productMask = productMask, + tokenSymbol = tokenSymbol, + tokenContractAddress = tokenContractAddress, + tokenDecimal = tokenDecimal, + issuerName = null, + manufactureDateTime = Calendar.getInstance().time, + manufacturerSignature = null) + + + val ndefs = mutableListOf() + if (from.uri.isNotEmpty()) { + ndefs.add(NdefRecord(NdefRecord.Type.URI, from.uri)) + } + when (from.aar) { + "None" -> null + "--- CUSTOM ---" -> NdefRecord(NdefRecord.Type.AAR, from.aarCustom) + else -> NdefRecord(NdefRecord.Type.AAR, from.aar) + }?.let { ndefs.add(it) } + + return CardConfig( + "Tangem", + "Tangem Test", + from.series, + from.startNumber, + 1000, + from.PIN, + from.PIN2, + from.PIN3, + from.hexCrExKey, + from.CVC, + from.pauseBeforePIN2.toInt(), + from.smartSecurityDelay, + EllipticCurve.byName(from.curveID) ?: EllipticCurve.Secp256k1, + signingMethod, + from.MaxSignatures.toInt(), + from.isReusable, + from.allowSwapPIN, + from.allowSwapPIN2, + from.useActivation, + from.useCVC, + from.useNDEF, + from.useDynamicNDEF, + from.oneApdu, + from.useBlock, + from.allowSelectBlockchain, + from.forbidPurgeWallet, + from.protocolAllowUnencrypted, + from.protocolAllowStaticEncryption, + from.protectIssuerDataAgainstReplay, + from.forbidDefaultPIN, + from.disablePrecomputedNDEF, + from.skipSecurityDelayIfValidatedByIssuer, + from.skipCheckPIN2andCVCIfValidatedByIssuer, + from.skipSecurityDelayIfValidatedByLinkedTerminal, + from.restrictOverwriteIssuerDataEx, + from.requireTerminalTxSignature, + from.requireTerminalCertSignature, + from.checkPIN3onCard, + from.createWallet, + cardData, + ndefs + ) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt deleted file mode 100644 index 6b9b041b49..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.tangemtest.ucase.variants.personalize.converter - -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.ItemsToModel -import com.tangem.tangemtest._arch.structure.abstraction.ModelConverter -import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems -import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.ItemsToPersonalizeConfig -import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.PersonalizeConfigToItem -import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig - -/** -[REDACTED_AUTHOR] - */ -class PersonalizeConfigConverter : ModelConverter { - - private val toModel: ItemsToModel = ItemsToPersonalizeConfig() - private val toItems: ModelToItems = PersonalizeConfigToItem() - - override fun convert(from: List, default: PersonalizeConfig): PersonalizeConfig { - return toModel.convert(from, default) - } - - override fun convert(from: PersonalizeConfig): List { - return toItems.convert(from) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt deleted file mode 100644 index 33a0c7196c..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo - -import com.tangem.tangemtest.ucase.variants.personalize.* - -class ItemTypes { - - val blockIdList = mutableListOf( - BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination, - BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc, - BlockId.SettingsMaskNdef, BlockId.Pins - ) - - val listItemList = mutableListOf(Common.Curve, Common.Blockchain, SettingsMaskNdef.Aar, Pins.PauseBeforePin2) - - val boolList = mutableListOf( - Common.CreateWallet, SigningMethod.SignTx, SigningMethod.SignTxRaw, SigningMethod.SignValidatedTx, - SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer, - SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig, - SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken, - ProductMask.Note, ProductMask.Tag, ProductMask.IdCard, ProductMask.IdIssuerCard,SettingsMask.IsReusable, SettingsMask.NeedActivation, - SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu, - SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin, - SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay, - SettingsMask.SkipSecurityDelayIfValidated, SettingsMask.SkipPin2CvcIfValidated, - SettingsMask.SkipSecurityDelayOnLinkedTerminal, SettingsMask.RestrictOverwriteExtraIssuerData, - SettingsMaskProtocolEnc.AllowUnencrypted, SettingsMaskProtocolEnc.AllowStaticEncryption, - SettingsMaskNdef.UseNdef, SettingsMaskNdef.DynamicNdef, SettingsMaskNdef.DisablePrecomputedNdef - ) - - val editTextList = mutableListOf( - CardNumber.Series, CardNumber.BatchId, Common.BlockchainCustom, SignHashExProp.CryptoExKey, Token.Symbol, - Token.ContractAddress, Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, SettingsMaskNdef.AarCustom - ) - - val numberList = mutableListOf( - CardNumber.Number, Common.MaxSignatures, SignHashExProp.PinLessFloorLimit, Denomination.Denomination, Token.Decimal - ) - - val hiddenList = mutableListOf( - CardNumber.Series, CardNumber.BatchId, Pins.Pin3, SigningMethod.SignExternal, - SignHashExProp.CryptoExKey, SignHashExProp.CheckPin3, SettingsMask.OneApdu, - SettingsMask.UseBlock, SettingsMask.ProtectIssuerDataAgainstReplay, - SignHashExProp.RequireTerminalCertSig, SignHashExProp.RequireTerminalTxSig - ) - -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt deleted file mode 100644 index af0514855f..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo - -import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.BaseItem -import com.tangem.tangemtest._arch.structure.abstraction.Block -import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tangemtest._arch.structure.abstraction.ItemsToModel -import com.tangem.tangemtest._arch.structure.impl.KeyValue -import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper -import com.tangem.tangemtest.ucase.variants.personalize.* -import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigValuesHolder -import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class ItemsToPersonalizeConfig : ItemsToModel { - protected val valuesHolder = PersonalizeConfigValuesHolder() - - override fun convert(from: List, default: PersonalizeConfig): PersonalizeConfig { - valuesHolder.init(default) - mapListItems(from) - return createModel() - } - - private fun mapListItems(itemList: List) { - itemList.forEach { item -> mapItemToHolder(item) } - } - - private fun mapItemToHolder(item: Item) { - when (item) { - is Block -> mapListItems(item.itemList) - is BaseItem<*> -> { - val defValue = valuesHolder.get(item.id) ?: return - defValue.set(item.viewModel.data) - } - } - } - - private fun createModel(): PersonalizeConfig { - val export = PersonalizeConfig() - export.series = getTyped(CardNumber.Series) - export.startNumber = getTyped(CardNumber.Number) - export.curveID = getTyped(Common.Curve) - export.blockchain = getTyped(Common.Blockchain) - export.blockchainCustom = getTyped(Common.BlockchainCustom) - export.MaxSignatures = getTyped(Common.MaxSignatures) - export.createWallet = getTyped(Common.CreateWallet) - export.SigningMethod0 = getTyped(SigningMethod.SignTx) - export.SigningMethod1 = getTyped(SigningMethod.SignTxRaw) - export.SigningMethod2 = getTyped(SigningMethod.SignValidatedTx) - export.SigningMethod3 = getTyped(SigningMethod.SignValidatedTxRaw) - export.SigningMethod4 = getTyped(SigningMethod.SignValidatedTxIssuer) - export.SigningMethod5 = getTyped(SigningMethod.SignValidatedTxRawIssuer) - export.SigningMethod6 = getTyped(SigningMethod.SignExternal) - export.pinLessFloorLimit = getTyped(SignHashExProp.PinLessFloorLimit) - export.hexCrExKey = getTyped(SignHashExProp.CryptoExKey) - export.requireTerminalCertSignature = getTyped(SignHashExProp.RequireTerminalCertSig) - export.requireTerminalTxSignature = getTyped(SignHashExProp.RequireTerminalTxSig) - export.checkPIN3onCard = getTyped(SignHashExProp.CheckPin3) - export.writeOnPersonalization = getTyped(Denomination.WriteOnPersonalize) - export.denomination = getTyped(Denomination.Denomination) - export.itsToken = getTyped(Token.ItsToken) - export.symbol = getTyped(Token.Symbol) - export.contractAddress = getTyped(Token.ContractAddress) - export.decimal = getTyped(Token.Decimal) - export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) } - export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) } - export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.IdCard) } - export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMask.IdIssuerCard) } - export.isReusable = getTyped(SettingsMask.IsReusable) - export.useActivation = getTyped(SettingsMask.NeedActivation) - export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge) - export.allowSelectBlockchain = getTyped(SettingsMask.AllowSelectBlockchain) - export.useBlock = getTyped(SettingsMask.UseBlock) - export.oneApdu = getTyped(SettingsMask.OneApdu) - export.useCVC = getTyped(SettingsMask.UseCvc) - export.allowSwapPIN = getTyped(SettingsMask.AllowSwapPin) - export.allowSwapPIN2 = getTyped(SettingsMask.AllowSwapPin2) - export.forbidDefaultPIN = getTyped(SettingsMask.ForbidDefaultPin) - export.smartSecurityDelay = getTyped(SettingsMask.SmartSecurityDelay) - export.protectIssuerDataAgainstReplay = getTyped(SettingsMask.ProtectIssuerDataAgainstReplay) - export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMask.SkipSecurityDelayIfValidated) - export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMask.SkipPin2CvcIfValidated) - export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMask.SkipSecurityDelayOnLinkedTerminal) - export.restrictOverwriteIssuerDataEx = getTyped(SettingsMask.RestrictOverwriteExtraIssuerData) - export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEnc.AllowUnencrypted) - export.protocolAllowStaticEncryption = getTyped(SettingsMaskProtocolEnc.AllowStaticEncryption) - export.useNDEF = getTyped(SettingsMaskNdef.UseNdef) - export.useDynamicNDEF = getTyped(SettingsMaskNdef.DynamicNdef) - export.disablePrecomputedNDEF = getTyped(SettingsMaskNdef.DisablePrecomputedNdef) - export.aar = getTyped(SettingsMaskNdef.Aar) - export.aarCustom = getTyped(SettingsMaskNdef.AarCustom) - export.PIN = getTyped(Pins.Pin) - export.PIN2 = getTyped(Pins.Pin2) - export.PIN3 = getTyped(Pins.Pin3) - export.CVC = getTyped(Pins.Cvc) - export.pauseBeforePIN2 = getTyped(Pins.PauseBeforePin2) - return export - } - - private inline fun getTyped(id: Id): Type { - return getTypedBy(valuesHolder, id)!! - } - - private inline fun getTypedBy(holder: PersonalizeConfigValuesHolder, id: Id): Type? { - Log.d(this, "getTyped for id: $id") - var typedValue = holder.get(id)?.get() - - typedValue = when (typedValue) { - is ListValueWrapper -> (typedValue.selectedItem as KeyValue).value as Type - else -> typedValue as? Type ?: null - } - return typedValue - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt deleted file mode 100644 index 5550a71bac..0000000000 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt +++ /dev/null @@ -1,301 +0,0 @@ -package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo - -import com.tangem.commands.CardData -import com.tangem.commands.EllipticCurve -import com.tangem.commands.ProductMaskBuilder -import com.tangem.commands.personalization.entities.CardConfig -import com.tangem.commands.personalization.entities.NdefRecord -import com.tangem.tangemtest._arch.structure.Additional -import com.tangem.tangemtest._arch.structure.Id -import com.tangem.tangemtest._arch.structure.abstraction.* -import com.tangem.tangemtest._arch.structure.impl.* -import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey -import com.tangem.tangemtest.ucase.variants.personalize.* -import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigValuesHolder -import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig -import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -class PersonalizeConfigToItem : ModelToItems { - private val valuesHolder = PersonalizeConfigValuesHolder() - private val itemTypes = ItemTypes() - - override fun convert(from: PersonalizeConfig): List { - valuesHolder.init(from) - val blocList = mutableListOf() - blocList.add(cardNumber()) - blocList.add(common()) - blocList.add(signingMethod()) - blocList.add(signHashExProperties()) - blocList.add(denomination()) - blocList.add(token()) - blocList.add(productMask()) - blocList.add(settingsMask()) - blocList.add(settingsMaskProtocolEnc()) - blocList.add(settingsMaskNdef()) - blocList.add(pins()) - val payloadBlock = ListItemBlock(Additional.JSON_TAILS) - addPayload(payloadBlock, from) - blocList.add(payloadBlock) - blocList.iterate { - if (itemTypes.hiddenList.contains(it.id)) { - (it as BaseItem<*>)?.viewModel.viewState.isHiddenField = true - } - } - return blocList - } - - private fun cardNumber(): Block { - val block = createBlock(BlockId.CardNumber) - mutableListOf( - CardNumber.Series, - CardNumber.Number, - CardNumber.BatchId - ).forEach { createItem(block, it as Id) } - return block - } - - private fun common(): Block { - val block = createBlock(BlockId.Common) - mutableListOf( - Common.Curve, - Common.Blockchain, - Common.BlockchainCustom, - Common.MaxSignatures, - Common.CreateWallet - ).forEach { createItem(block, it) } - return block - } - - private fun signingMethod(): Block { - val block = createBlock(BlockId.SigningMethod) - mutableListOf( - SigningMethod.SignTx, - SigningMethod.SignTxRaw, - SigningMethod.SignValidatedTx, - SigningMethod.SignValidatedTxRaw, - SigningMethod.SignValidatedTxIssuer, - SigningMethod.SignValidatedTxRawIssuer, - SigningMethod.SignExternal - ).forEach { createItem(block, it) } - return block - } - - private fun signHashExProperties(): Block { - val block = createBlock(BlockId.SignHashExProp) - mutableListOf( - SignHashExProp.PinLessFloorLimit, - SignHashExProp.CryptoExKey, - SignHashExProp.RequireTerminalCertSig, - SignHashExProp.RequireTerminalTxSig, - SignHashExProp.CheckPin3 - ).forEach { createItem(block, it) } - return block - } - - private fun denomination(): Block { - val block = createBlock(BlockId.Denomination) - mutableListOf( - Denomination.WriteOnPersonalize, - Denomination.Denomination - ).forEach { createItem(block, it) } - return block - } - - private fun token(): Block { - val block = createBlock(BlockId.Token) - mutableListOf( - Token.ItsToken, - Token.Symbol, - Token.ContractAddress, - Token.Decimal - ).forEach { createItem(block, it) } - return block - } - - private fun productMask(): Block { - val block = createBlock(BlockId.ProdMask) - mutableListOf( - ProductMask.Note, - ProductMask.Tag, - ProductMask.IdCard, - ProductMask.IdIssuerCard - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMask(): Block { - val block = createBlock(BlockId.SettingsMask) - mutableListOf( - SettingsMask.IsReusable, - SettingsMask.NeedActivation, - SettingsMask.ForbidPurge, - SettingsMask.AllowSelectBlockchain, - SettingsMask.UseBlock, - SettingsMask.OneApdu, - SettingsMask.UseCvc, - SettingsMask.AllowSwapPin, - SettingsMask.AllowSwapPin2, - SettingsMask.ForbidDefaultPin, - SettingsMask.SmartSecurityDelay, - SettingsMask.ProtectIssuerDataAgainstReplay, - SettingsMask.SkipSecurityDelayIfValidated, - SettingsMask.SkipPin2CvcIfValidated, - SettingsMask.SkipSecurityDelayOnLinkedTerminal, - SettingsMask.RestrictOverwriteExtraIssuerData - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMaskProtocolEnc(): Block { - val block = createBlock(BlockId.SettingsMaskProtocolEnc) - mutableListOf( - SettingsMaskProtocolEnc.AllowUnencrypted, - SettingsMaskProtocolEnc.AllowStaticEncryption - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMaskNdef(): Block { - val block = createBlock(BlockId.SettingsMaskNdef) - mutableListOf( - SettingsMaskNdef.UseNdef, - SettingsMaskNdef.DynamicNdef, - SettingsMaskNdef.DisablePrecomputedNdef, - SettingsMaskNdef.Aar, - SettingsMaskNdef.AarCustom - ).forEach { createItem(block, it) } - return block - } - - private fun pins(): Block { - val block = createBlock(BlockId.Pins) - mutableListOf(Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, Pins.PauseBeforePin2) - .forEach { createItem(block, it) } - return block - } - - private fun createBlock(id: Id): ListItemBlock { - return ListItemBlock(id).apply { addItem(TextItem(id)) } - } - - private fun addPayload(block: Block, from: PersonalizeConfig) { - block.payload[PayloadKey.incomingJson] = from - } - - private fun createItem(block: ListItemBlock, id: Id) { - val holder = valuesHolder.get(id) ?: return - val item = when { - itemTypes.blockIdList.contains(id) -> TextItem(id, holder.get() as? String) - itemTypes.listItemList.contains(id) -> ListItem(id, holder.list as List, holder.get()) - itemTypes.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean) - itemTypes.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String) - itemTypes.numberList.contains(id) -> NumberItem(id, holder.get() as? Number) - else -> ListItemBlock(Additional.UNDEFINED) - } - block.addItem(item) - } -} - -class PersonalizeConfigToCardConfig : Converter { - - override fun convert(from: PersonalizeConfig): CardConfig { - val signingMethod = com.tangem.commands.SigningMethod.build( - signHash = from.SigningMethod0, - signRaw = from.SigningMethod1, - signHashValidatedByIssuer = from.SigningMethod2, - signRawValidatedByIssuer = from.SigningMethod3, - signHashValidatedByIssuerAndWriteIssuerData = from.SigningMethod4, - signRawValidatedByIssuerAndWriteIssuerData = from.SigningMethod5, - signPos = from.SigningMethod6 - ) - - val isNote = from.cardData.product_note - val isTag = from.cardData.product_tag - val isIdCard = from.cardData.product_id_card - val isIdIssuer = from.cardData.product_id_issuer - - val productMaskBuilder = ProductMaskBuilder() - if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note) - if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag) - if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard) - if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.ProductMask.idIssuer) - val productMask = productMaskBuilder.build() - - var tokenSymbol: String? = null - var tokenContractAddress: String? = null - var tokenDecimal: Int? = null - if (from.itsToken) { - tokenSymbol = from.symbol - tokenContractAddress = from.contractAddress - tokenDecimal = from.decimal.toInt() - } - - val blockchain = if (from.blockchain.isNotEmpty()) from.blockchain else from.blockchainCustom - val cardData = CardData( - blockchainName = blockchain, - batchId = from.batchId, - productMask = productMask, - tokenSymbol = tokenSymbol, - tokenContractAddress = tokenContractAddress, - tokenDecimal = tokenDecimal, - issuerName = null, - manufactureDateTime = Calendar.getInstance().time, - manufacturerSignature = null) - - - val ndefs = mutableListOf() - when (from.aar) { - "None" -> null - "--- CUSTOM ---" -> NdefRecord(NdefRecord.Type.AAR, from.aarCustom) - else -> NdefRecord(NdefRecord.Type.AAR, from.aar) - }?.let { ndefs.add(it) } - - return CardConfig( - "Tangem", - "Tangem Test", - from.series, - from.startNumber, - 1000, - from.PIN, - from.PIN2, - from.PIN3, - from.hexCrExKey, - from.CVC, - from.pauseBeforePIN2.toInt(), - from.smartSecurityDelay, - EllipticCurve.byName(from.curveID) ?: EllipticCurve.Secp256k1, - signingMethod, - from.MaxSignatures.toInt(), - from.isReusable, - from.allowSwapPIN, - from.allowSwapPIN2, - from.useActivation, - from.useCVC, - from.useNDEF, - from.useDynamicNDEF, - from.oneApdu, - from.useBlock, - from.allowSelectBlockchain, - from.forbidPurgeWallet, - from.protocolAllowUnencrypted, - from.protocolAllowStaticEncryption, - from.protectIssuerDataAgainstReplay, - from.forbidDefaultPIN, - from.disablePrecomputedNDEF, - from.skipSecurityDelayIfValidatedByIssuer, - from.skipCheckPIN2andCVCIfValidatedByIssuer, - from.skipSecurityDelayIfValidatedByLinkedTerminal, - from.restrictOverwriteIssuerDataEx, - from.requireTerminalTxSignature, - from.requireTerminalCertSignature, - from.checkPIN3onCard, - from.createWallet, - cardData, - ndefs - ) - } -} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt similarity index 96% rename from tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt index c8d9afa0d1..ace112c0af 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt @@ -1,15 +1,15 @@ package com.tangem.tangemtest.ucase.variants.personalize.dto +import com.tangem.KeyPair import com.tangem.commands.personalization.entities.Acquirer import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer -import com.tangem.common.KeyPair /** [REDACTED_AUTHOR] */ -interface DefaultPersonalizeParams { +interface DefaultPersonalizationParams { companion object { fun issuer(): Issuer { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizationConfig.kt similarity index 87% rename from tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizationConfig.kt index 0ebc993776..2bfd50514c 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizationConfig.kt @@ -3,7 +3,7 @@ package com.tangem.tangemtest.ucase.variants.personalize.dto /** [REDACTED_AUTHOR] */ -class PersonalizeConfig { +class PersonalizationConfig { // Card number var series = "BB" @@ -15,8 +15,8 @@ class PersonalizeConfig { var curveID = "ed25519" var blockchain = "BTC/test" var blockchainCustom = "" - var MaxSignatures: Long = 100 - var createWallet = false + var MaxSignatures: Long = 999999 + var createWallet = true // Signing method var SigningMethod0 = true @@ -62,7 +62,7 @@ class PersonalizeConfig { var allowSwapPIN = true var allowSwapPIN2 = true var forbidDefaultPIN = false - var smartSecurityDelay = false + var smartSecurityDelay = true var protectIssuerDataAgainstReplay = true var skipSecurityDelayIfValidatedByIssuer = true var skipCheckPIN2andCVCIfValidatedByIssuer = true @@ -80,6 +80,7 @@ class PersonalizeConfig { var disablePrecomputedNDEF = false var aar = "com.tangem.wallet" var aarCustom = "" + var uri = "https://tangem.com" // Pins @@ -87,15 +88,8 @@ class PersonalizeConfig { var PIN2 = "000" var PIN3 = "" var CVC = "000" - var pauseBeforePIN2: Long = 15000 + var pauseBeforePIN2: Long = 5000L - - // not used -// var count: Long = 20 -// var numberFormat = "" -// var issuerData = null -// var releaseVersion = false -// var issuerName = "TANGEM SDK" } class CardData { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt new file mode 100644 index 0000000000..40133ebfd6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt @@ -0,0 +1,96 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.lifecycle.Observer +import com.tangem.commands.Card +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.StringId +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.widget.WidgetBuilder +import com.tangem.tangemtest.commons.view.MultiActionView +import com.tangem.tangemtest.commons.view.ViewAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizationItemsManager +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.tunnel.ActionView +import com.tangem.tangemtest.ucase.tunnel.ItemError +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment +import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder +import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class PersonalizationFragment : BaseCardActionFragment() { + + override val itemsManager: ItemsManager by lazy { PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) } + + override fun getLayoutId(): Int = R.layout.fg_personalization + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + lifecycle.addObserver(itemsManager as PersonalizationItemsManager) + } + + override fun widgetsWasCreated() { + super.widgetsWasCreated() + + val btnContainer = itemContainer.inflate(R.layout.view_simple_button) + val btn = btnContainer.findViewById