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 e372153248..af0e844c61 100644 --- a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -19,11 +19,13 @@ import org.junit.Test internal class WalletManagerFactoryTest { + private val sessionEnvironment = SessionEnvironment() + @Test fun createBitcoinWalletManager() { val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -34,7 +36,7 @@ internal class WalletManagerFactoryTest { fun createEthereumWalletManager() { val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -45,7 +47,7 @@ internal class WalletManagerFactoryTest { fun createStellarWalletManager() { val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -56,7 +58,7 @@ internal class WalletManagerFactoryTest { fun createCardanoWalletManager() { val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -67,7 +69,7 @@ internal class WalletManagerFactoryTest { fun createXrpWalletManager() { val data = "0108cb21000000002154200b534d4152542043415348000201028006322e31317200034104bdad63848f97c535da53cf8fd300d24fa33f0516d194aa78ec164a06994d00204bae243a424e316c6ec845e02d9b15eafae8c19018a926b0b7435e6e941cdadb0a0400007e210c5a81020028820407e30502830754414e47454d00840358525086400ed8734b877869722c7d0b37ffb154b9fef21c54bf2c6496feb1fb5c1fc28a2ac28e201dde84f27495fa7f08b3ca2be2fb4954bf0fe78af027d6cdc16c3eee923041048196aa4b410ac44a3b9cce18e7be226aea070acc83a9cf67540fac49af25129f6a538a28ad6341358e3c4f9963064f7e365372a651d374e5c23cdd37fd099bf2050a736563703235366b31000804000f4240070100090205dc604104d2b9fb288540d54e5b32ecaf0381cd571f97f6f1ecd036b66bb11aa52ffe9981110d883080e2e255c6b1640586f7765e6faa325d1340f49b56b83d9de56bc7ed6204000f42406304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -78,7 +80,7 @@ internal class WalletManagerFactoryTest { fun createBinanceWalletManager() { val data = "0108BB00000000000015200754414E47454D00020102800A322E3432642053444B0003410446D4155890B08BE217F0B1FA7DCCB16138C24B3E825A27315D5E4BBD6CAF76A28C7902007052BC1347355A78D54BD73216C9431D555CED827B54FD9255EB3A830A04041E76310C658102FFFF8A0101820407E40410830B54414E47454D2053444B00840742494E414E4345864029F115878EDC7B0CB2A6F4A4009447DCB43BBE922D7629AEBD0C9A910AD1E3BF15AE409C4F579700951ED2FE4D775171A86CFA8E50009A05938CE210D6D4A2583041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A0070100604104E3F3BE3CE3D8284DB3BA073AD0291040093D83C11A277B905D5555C9EC41073E103F4D9D299EDEA8285C51C3356A8681A545618C174251B984DF841F49D2376F62040001869F6304000000010F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -89,7 +91,7 @@ internal class WalletManagerFactoryTest { fun createBitcoinCashWalletManager() { val data = "0108BB00000000000049200754414E47454D00020102800A322E3432642053444B00034104766A1586D164B436E5D420AED01FDAB41B2AE7EDF0C865D7AF1DA995D70AB297E5B94B761CFBB405084C21BC97C02B4A1EA9ED4F515576EAB4D83AD3A0DFAA8A0A04041E76310C618102FFFF8A0101820407E4041B830B54414E47454D2053444B00840342434886408058F0F628C2466B09ECEB13F2A8EFDD4558F5D2DBDA9BD0628EE8C8CC99A778FF0F1AECD35704B9F3518486EA5C1D20F9DFCBAA66184F4CCCD9282E2632882C3041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A0070100604104BE37CD5251C8999EDBBFC759D800EB41E4DCB718289601EB15819404E1B2F2ED90FE50C2A481D06EC790D1EF6184974EB655ABAE4BE56A6D1C9E1A17B1EFDF0262040001869A6304000000060F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -100,7 +102,7 @@ internal class WalletManagerFactoryTest { fun createLitecoinWalletManager() { val data = "0108BB00000000000023200754414E47454D00020102800A322E3432642053444B000341043539F86A40ADD04CE165764A761FD3E4D251028615D2A573B1C3AE652E60AFDBFAF02E3239E89EF2C43FA448A327557ADC5AF36376A0574570F6DBD20113514A0A04041E76310C618102FFFF8A0101820407E40414830B54414E47454D2053444B0084034C5443864004BDEAD0117544886346CB47F7CA84ABA8C34239502F23D28595A4B16CAD72F7DE506BA818B86A649C2BB945986D4574993B3B755B47CBEE31C4FB931F6748183041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A00701006041044A76C9A70422160F515F956D0F50C71BBBA4F9862A22913817D63F0B1EF7C2FAF512E1C91B1BE827560EFE24FB1652B47337E296C778DFB1014D080CDD35EF6562040001869D6304000000030F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -111,7 +113,7 @@ internal class WalletManagerFactoryTest { fun createTezosWalletManager() { val data = "0108BB00000000000080200754414E47454D00020102800A322E3432642053444B0003410436CFC5D0A11353AE6AFEEDC84A2D02B2635C044DEEE47F99913072B8D166D14E557230AC5FB5272F1A0E523332CCE1A744B51DB53102FF7D3FDE023DC3477C460A04041E76310C638102FFFF8A0101820407E40514830B54414E47454D2053444B00840554455A4F538640C752685B29333CFB0DB0A7347579A0AE763F2B5C4BB09FD68E0B81A06CD01EC51347001732815A3ECFFCD78DDE4E53877581B9E4914B069570629D0C40A771B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050865643235353139000804000186A0070100602098E0E504F3A5FDE704400302ABB0A2EFB0DF0F95C166C91D7F207DEDCE10CBA362040001869F6304000000010F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -122,7 +124,7 @@ internal class WalletManagerFactoryTest { fun createDucatusWalletManager() { val data = "0108BB00000000000098200754414E47454D00020102800A322E3432642053444B000341041B5FD7C590938E836B388B996AE451FDED54F625FF2CF05E26E5AADF6F690AEF125E3D0F23CB6B8D1F78040DF6F71B40D098F2D8BE504DDEE2E1F99BEADD90500A04041E76310C618102FFFF8A0101820407E40515830B54414E47454D2053444B00840344554386401B453C10A092A3448FA83CAFD4FC3D7EB5EA1BBBDD6A020CAAC8CED36BB2661F41EF0B0C7418214F670DFE1200DCE18597158119BEF6CC52A4FF3B7E021A53B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A007010060410485D520C8B907F0BC5E03FCBBAC212CCD270764BBFF4990A28653A2FB0D656C342DF143C4D52C43582289E20A81D5D014C1384A1FFFEA1D121903AD7ED35A01EA62040001869D6304000000030F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) diff --git a/build.gradle b/build.gradle index 40bc31028b..bbc8c94420 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,9 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' classpath 'com.google.firebase:perf-plugin:1.3.1' + classpath 'com.squareup.sqldelight:gradle-plugin:1.4.0' } } diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index 8b71b5e8bf..ce4d4d4c3e 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -4,6 +4,7 @@ apply plugin: 'com.github.dcendents.android-maven' apply from: '../dependencies.gradle' apply from: '../jitpack.gradle' apply plugin: 'kotlin-kapt' +apply plugin: 'com.squareup.sqldelight' group = "$jitpackSdk.group" version "$jitpackSdk.version" @@ -11,7 +12,7 @@ version "$jitpackSdk.version" dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlidn" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7" @@ -22,6 +23,7 @@ dependencies { // misc implementation 'com.google.code.gson:gson:2.8.6' + implementation "com.squareup.sqldelight:sqlite-driver:1.4.0" //network implementation 'com.squareup.retrofit2:retrofit:2.8.1' @@ -36,6 +38,12 @@ dependencies { testImplementation "com.google.truth:truth:1.0.1" } +sqldelight { + Database { + packageName = "com.tangem" + } +} + sourceCompatibility = "8" targetCompatibility = "8" diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index bcaf32f1a7..7b0ec51200 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,15 +1,18 @@ package com.tangem +import com.tangem.commands.Command import com.tangem.commands.CommandResponse import com.tangem.commands.OpenSessionCommand import com.tangem.commands.ReadCommand import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.getType import com.tangem.crypto.EncryptionHelper import com.tangem.crypto.pbkdf2Hash +import com.tangem.tasks.PinType import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -18,7 +21,7 @@ import kotlinx.coroutines.flow.* */ interface CardSessionRunnable { - val performPreflightRead: Boolean + val requiresPin2: Boolean /** * The starting point for custom business logic. @@ -53,7 +56,7 @@ enum class TagType { * If null, a default header and text body will be used. */ class CardSession( - val environment: SessionEnvironment, + private val environmentService: SessionEnvironmentService, private val reader: CardReader, val viewDelegate: SessionViewDelegate, private var cardId: String? = null, @@ -73,8 +76,13 @@ class CardSession( private val tag = this.javaClass.simpleName + private var performPreflightRead = true + private var pin2Required = false + + val environment = environmentService.createEnvironment(cardId) + /** - * This metod starts a card session, performs preflight [ReadCommand], + * This method 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. @@ -83,7 +91,10 @@ class CardSession( runnable: T, callback: (result: CompletionResult) -> Unit ) { - start(runnable.performPreflightRead) { session, error -> + if ((runnable as? Command<*>)?.performPreflightRead == false) performPreflightRead = false + pin2Required = runnable.requiresPin2 + + start() { session, error -> if (error != null) { callback(CompletionResult.Failure(error)) return@start @@ -113,7 +124,6 @@ class CardSession( * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. */ fun start( - performPreflightRead: Boolean = true, callback: (session: CardSession, error: TangemSdkError?) -> Unit ) { @@ -121,6 +131,26 @@ class CardSession( callback(this, TangemSdkError.Busy()) return } + + + if (environment.pin1 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested(PinType.Pin1) { + environment.pin1 = PinCode(it) + start(callback) + } + return + } + + if (pin2Required && environment.pin2 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested(PinType.Pin2) { + environment.pin2 = PinCode(it) + start(callback) + } + return + } + state = CardSessionState.Active viewDelegate.onSessionStarted(cardId) @@ -174,6 +204,7 @@ class CardSession( return@run } environment.card = result.data + environmentService.updateEnvironment(environment, result.data.cardId) cardId = receivedCardId callback(this, null) } @@ -209,6 +240,7 @@ class CardSession( } private fun stopSession() { + environmentService.saveEnvironmentValues(environment, cardId) reader.stopSession() state = CardSessionState.Inactive scope.cancel() @@ -259,7 +291,7 @@ class CardSession( return CompletionResult.Failure(error) } val uid = result.uid - val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) + val protocolKey = environment.pin1!!.value.pbkdf2Hash(uid, 50) val secret = encryptionHelper.generateSecret(result.sessionKeyB) val sessionKey = (secret + protocolKey).calculateSha256() environment.encryptionKey = sessionKey diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 45da4b6912..7b643c5e1f 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -36,6 +36,18 @@ class Config( */ val cardFilter: CardFilter = CardFilter(), - var handleErrors: Boolean = true + var handleErrors: Boolean = true, -) \ No newline at end of file + var defaultPin1: String = DEFAULT_PIN_1, + + var defaultPin2: String = DEFAULT_PIN_2, + + var savePin1InStaticField: Boolean = true, + + var savePin2InStaticField: Boolean = false +) { + companion object { + const val DEFAULT_PIN_1 = "000000" + const val DEFAULT_PIN_2 = "000" + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 88050df353..e8afbb33e5 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -2,7 +2,7 @@ package com.tangem import com.tangem.commands.Card import com.tangem.commands.EllipticCurve -import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.PinCode import com.tangem.crypto.CryptoUtils.generatePublicKey @@ -10,42 +10,38 @@ 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 [SessionEnvironment]. * - * @property card Current card, read by preflight [com.tangem.commands.ReadCommand]. - * @property terminalKeys generated terminal keys used in Linked Terminal feature. + * @param cardId Card ID, if it is known before tapping the card. + * @property config sets a number of parameters for communication with Tangem cards. + * @param terminalKeysService is used to retrieve terminal keys used in Linked Terminal feature. + * @param cardValuesStorage is used to save and retrieve some values relating to a particular card. + * + * @property pin1 An access Code, required to get access to a card. A default value is set in [Config] + * @property pin2 A code, required to perform particular operations with a card. A default value is set in [Config] + * @property terminalKeys generated terminal keys used in Linked Terminal feature + * @property cardFilter a property that defines types of card that this SDK will be able to interact with + * @property handleErrors if true, the SDK parses internal card errors into concrete [TangemSdkError] + * @property encryptionMode preferred [EncryptionMode] for interaction with cards + * @property encryptionKey is used for encrypted communication with a card + * */ -data class SessionEnvironment( - var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(), - var card: Card? = null, +class SessionEnvironment( + var pin1: PinCode? = PinCode(Config.DEFAULT_PIN_1, true), + var pin2: PinCode? = PinCode(Config.DEFAULT_PIN_2, true), + var cvc: ByteArray? = null, + var terminalKeys: KeyPair? = null, + var cardFilter: CardFilter = CardFilter(), + val handleErrors: Boolean = true, + var encryptionMode: EncryptionMode = EncryptionMode.NONE, var encryptionKey: ByteArray? = null, - var cvc: ByteArray? = null, - var cardFilter: CardFilter = CardFilter(), - val handleErrors: Boolean = true -) { - var pin1: ByteArray = SessionEnvironment.pin1 - private set - get() = SessionEnvironment.pin1 + var cardVerification: VerificationState = VerificationState.NotVerified, + var cardValidation: VerificationState = VerificationState.NotVerified, + var codeVerification: VerificationState = VerificationState.NotVerified, - fun isCurrentPin1Default(): Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) - fun isCurrentPin2Default(): Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256()) - - fun setPin1(pin1: String) { - SessionEnvironment.pin1 = pin1.calculateSha256() - } - - fun setPin2(pin2: String) { - this.pin2 = pin2.calculateSha256() - } - - companion object { - const val DEFAULT_PIN = "000000" - const val DEFAULT_PIN2 = "000" - - var pin1: ByteArray = DEFAULT_PIN.calculateSha256() - } -} + var card: Card? = null +) /** * All possible encryption modes. @@ -60,4 +56,8 @@ class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) { constructor(privateKey: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1) : this(generatePublicKey(privateKey, curve), privateKey) +} + +enum class VerificationState { + Passed, Offline, Failed, NotVerified, Cancelled } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt new file mode 100644 index 0000000000..d36c95c8ad --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt @@ -0,0 +1,77 @@ +package com.tangem + +import com.tangem.common.CardValuesStorage +import com.tangem.common.PinCode +import com.tangem.common.TerminalKeysService + +class SessionEnvironmentService( + private val config: Config, + private val terminalKeysService: TerminalKeysService?, + private val cardValuesStorage: CardValuesStorage? +) { + + fun createEnvironment(cardId: String?): SessionEnvironment { + val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + + val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) } + + val cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + val cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + val codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + val pin1 = TangemSdk.pin1 + ?: if (cardValues?.isPin1Default != false) { + PinCode(config.defaultPin1, true) + } else { + null + } + + val pin2 = cardId?.let { TangemSdk.pin2[it] } + ?: if (cardValues?.isPin2Default != false) { + PinCode(config.defaultPin2, true) + } else { + null + } + + return SessionEnvironment( + terminalKeys = terminalKeys, + cardFilter = config.cardFilter, + handleErrors = config.handleErrors, + + encryptionMode = config.encryptionMode, + + cardVerification = cardVerification, + cardValidation = cardValidation, + codeVerification = codeVerification, + + pin1 = pin1, + pin2 = pin2 + ) + } + + fun updateEnvironment(environment: SessionEnvironment, cardId: String) { + val cardValues = cardId.let { cardValuesStorage?.getValues(it) } + environment.cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + environment.cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + environment.codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + if (cardValues?.isPin1Default == false && environment.pin1?.isDefault == true) environment.pin1 = null + if (cardValues?.isPin2Default == false && environment.pin1?.isDefault == true) environment.pin2 = null + } + + fun saveEnvironmentValues(environment: SessionEnvironment, cardId: String?) { + if (config.savePin1InStaticField) { + TangemSdk.pin1 = environment.pin1 + } + if (config.savePin2InStaticField) { + cardId?.let { cardId -> TangemSdk.pin2[cardId] = environment.pin2 } + } + cardId?.let { cardId -> + cardValuesStorage?.saveValues(cardId, + environment.pin1?.isDefault ?: false, + environment.pin2?.isDefault ?: false, + environment.cardVerification, environment.cardVerification, + environment.codeVerification) + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index e3b2306bbf..a62716454b 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,5 +1,7 @@ package com.tangem +import com.tangem.tasks.PinType + /** * Allows interaction with users and shows visual elements. * @@ -47,7 +49,12 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (pin: String?) -> Unit) + fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) + + /** + * It is called when a user wants to change pin code. + */ + fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 6508fb8c3f..40b0a79848 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -10,10 +10,14 @@ import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse +import com.tangem.common.CardValuesStorage import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils +import com.tangem.tasks.ChangePinTask import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.PinType import com.tangem.tasks.ScanTask /** @@ -26,14 +30,19 @@ import com.tangem.tasks.ScanTask * 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. + * @property terminalKeysService allows to retrieve saved terminal keys. */ class TangemSdk( private val reader: CardReader, private val viewDelegate: SessionViewDelegate, - var config: Config = Config() + var config: Config = Config(), + cardValuesStorage: CardValuesStorage, + terminalKeysService: TerminalKeysService? = null ) { - private var terminalKeysService: TerminalKeysService? = null + private val environmentService = SessionEnvironmentService( + config, terminalKeysService, cardValuesStorage + ) init { CryptoUtils.initCrypto() @@ -203,7 +212,7 @@ class TangemSdk( initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit ) { - val command = WriteUserDataCommand(userData = userData,userCounter = userCounter) + val command = WriteUserDataCommand(userData = userData, userCounter = userCounter) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -361,6 +370,30 @@ class TangemSdk( startSessionWithRunnable(command, null, initialMessage, callback) } + fun changePin1(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinTask(PinType.Pin1, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin2(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinTask(PinType.Pin2, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin3(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinTask(PinType.Pin3, pin) + startSessionWithRunnable(command, cardId, 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], @@ -378,13 +411,13 @@ class TangemSdk( fun startSessionWithRunnable( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + val cardSession = CardSession(environmentService, 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]. + * Tangem SDK will start a card session 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 @@ -395,27 +428,13 @@ class TangemSdk( * then you can use the [CardSession] to interact with a card. */ fun startSession(cardId: String? = null, initialMessage: Message? = null, - callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + callback: (session: CardSession, error: TangemSdkError?) -> Unit) { + val cardSession = CardSession(environmentService, reader, viewDelegate, cardId, initialMessage) Thread().run { cardSession.start(callback = 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 + companion object { + var pin1: PinCode? = null + val pin2: MutableMap = mutableMapOf() } - - private fun buildEnvironment(): SessionEnvironment { - val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null - return SessionEnvironment( - terminalKeys = terminalKeys, - cardFilter = config.cardFilter, - handleErrors = config.handleErrors - ) - } - - 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 672ca2da62..7b7116ba44 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -82,7 +82,7 @@ class CheckWalletCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index ce21f6cdba..870cf3b446 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -2,9 +2,11 @@ package com.tangem.commands import com.tangem.* import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.apdu.* import com.tangem.common.extensions.toInt import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.PinType interface ApduSerializable { @@ -37,8 +39,8 @@ interface CommandResponse */ abstract class Command : ApduSerializable, CardSessionRunnable { - override val performPreflightRead: Boolean = true - open val requiresPin2: Boolean = false + open val performPreflightRead: Boolean = true + override val requiresPin2: Boolean = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") @@ -50,7 +52,6 @@ abstract class Command : ApduSerializable, CardSessionRu open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error fun transceive(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card if (session.environment.handleErrors && card != null) { performPreCheck(card)?.let { error -> @@ -58,12 +59,14 @@ abstract class Command : ApduSerializable, CardSessionRu return } } - - if (requiresPin2 && session.environment.isCurrentPin2Default()) { + if (requiresPin2 && session.environment.pin2?.isDefault == true) { handlePin2(session, callback) return } + transceiveInternal(session, callback) + } + private fun transceiveInternal(session: CardSession, callback: (result: CompletionResult) -> Unit) { val apdu = serialize(session.environment) transceiveApdu(apdu, session) { result -> when (result) { @@ -71,9 +74,15 @@ abstract class Command : ApduSerializable, CardSessionRu if (session.environment.handleErrors) { val error = mapError(session.environment.card, result.error) if (error is TangemSdkError.Pin1Required) { + session.environment.pin1 = null handlePin1(session, callback) return@transceiveApdu } + if (error is TangemSdkError.Pin2OrCvcRequired) { + session.environment.pin2 = null + handlePin2(session, callback) + return@transceiveApdu + } callback(CompletionResult.Failure(error)) return@transceiveApdu @@ -93,9 +102,9 @@ abstract class Command : ApduSerializable, CardSessionRu } private fun transceiveApdu( - apdu: CommandApdu, - session: CardSession, - callback: (result: CompletionResult) -> Unit + apdu: CommandApdu, + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}") @@ -114,17 +123,17 @@ abstract class Command : ApduSerializable, CardSessionRu StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = - deserializeSecurityDelay(responseApdu) + deserializeSecurityDelay(responseApdu) if (remainingTime != null) { session.viewDelegate.onSecurityDelay( - remainingTime, - session.environment.card?.pauseBeforePin2 ?: 0 + remainingTime, + session.environment.card?.pauseBeforePin2 ?: 0 ) } Log.i( - this::class.simpleName!!, - "Nfc command ${this::class.simpleName!!} " + - "triggered security delay of $remainingTime milliseconds" + this::class.simpleName!!, + "Nfc command ${this::class.simpleName!!} " + + "triggered security delay of $remainingTime milliseconds" ) transceiveApdu(apdu, session, callback) } @@ -149,7 +158,7 @@ abstract class Command : ApduSerializable, CardSessionRu } else -> { val error = responseApdu.statusWord.toTangemSdkError() - if (error != null && !tryHandleError(error)) { + if (error != null) { callback(CompletionResult.Failure(error)) } else { callback(CompletionResult.Failure(TangemSdkError.UnknownError())) @@ -173,61 +182,56 @@ abstract class Command : ApduSerializable, CardSessionRu * @return Remaining security delay in milliseconds. */ private fun deserializeSecurityDelay( - responseApdu: ResponseApdu + responseApdu: ResponseApdu ): Int? { val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } - private fun tryHandleError(error: TangemSdkError): Boolean { - return false - } - private fun handlePin1( - session: CardSession, - callback: (result: CompletionResult) -> Unit + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { - if (!session.environment.isCurrentPin1Default()) { - session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) - transceive(session, callback) - return - } session.pause() - session.viewDelegate.onPinRequested { pin1 -> - if (!pin1.isNullOrEmpty()) { - session.environment.setPin1(pin1) - session.resume() - transceive(session, callback) - } else { - session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) - session.resume() - transceive(session, callback) - } + session.viewDelegate.onPinRequested(PinType.Pin1) { pin1 -> + session.environment.pin1 = PinCode(pin1) + session.resume() + transceive(session, callback) } } private fun handlePin2( - session: CardSession, - callback: (result: CompletionResult) -> Unit + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { - val checkPinCommand = SetPinCommand(session.environment.pin1, session.environment.pin2) + val currentPin1 = session.environment.pin1?.value ?: run { + callback(CompletionResult.Failure(TangemSdkError.Pin1Required())) + return + } + val currentPin2 = session.environment.pin2?.value ?: run { + callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) + return + } + + val checkPinCommand = SetPinCommand(currentPin1, currentPin2) checkPinCommand.run(session) { result -> when (result) { is CompletionResult.Failure -> { - session.viewDelegate.onPinRequested { pin2 -> - if (!pin2.isNullOrEmpty()) { - session.environment.setPin2(pin2) - transceive(session, callback) - } else { - session.environment.setPin2(SessionEnvironment.DEFAULT_PIN2) - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - } - } + session.environment.pin2 = null + getPin2FromDelegate(session, callback) } is CompletionResult.Success -> { - transceive(session, callback) + transceiveInternal(session, callback) } } } } + + private fun getPin2FromDelegate(session: CardSession, + callback: (result: CompletionResult) -> Unit) { + session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 -> + session.environment.pin2 = PinCode(pin2) + transceive(session, callback) + } + } } \ 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 83a6cbd78d..af6cce48b8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -62,9 +62,9 @@ class CreateWalletCommand : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.Cvc, environment.cvc) return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } 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 c4ca477ca3..3d0631f3df 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -61,9 +61,9 @@ class PurgeWalletCommand : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } 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 189588a080..5e692f2e40 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -382,7 +382,7 @@ class ReadCommand : Command() { * 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, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) return CommandApdu(Instruction.Read, tlvBuilder.serialize()) } 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 97bc306083..4d2301675a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -96,7 +96,7 @@ class ReadIssuerDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) 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 cb7bba09d0..88e5af5569 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -145,7 +145,7 @@ class ReadIssuerExtraDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) 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 2e68b631ef..58baea5b08 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -62,7 +62,7 @@ class ReadUserDataCommand : Command() { 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.Pin, environment.pin1?.value) return CommandApdu(Instruction.ReadUserData, builder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt index 084e755cc1..6b7f12fa80 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt @@ -6,7 +6,6 @@ import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.apdu.StatusWord -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag @@ -40,28 +39,27 @@ enum class SetPinStatus { } class SetPinResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * - */ - val status: SetPinStatus + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + /** + * + */ + val status: SetPinStatus ) : CommandResponse - class SetPinCommand( - private val newPin1: ByteArray = SessionEnvironment.DEFAULT_PIN.calculateSha256(), - private val newPin2: ByteArray = SessionEnvironment.DEFAULT_PIN2.calculateSha256(), - private val newPin3: ByteArray? = null + private var newPin1: ByteArray, + private var newPin2: ByteArray, + private var newPin3: ByteArray? = null ) : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.Cvc, environment.cvc) tlvBuilder.append(TlvTag.NewPin, newPin1) tlvBuilder.append(TlvTag.NewPin2, newPin2) @@ -72,12 +70,13 @@ class SetPinCommand( override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SetPinResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - val status = SetPinStatus.fromStatusWord(apdu.statusWord) ?: throw TangemSdkError.DecodingFailed() + val status = SetPinStatus.fromStatusWord(apdu.statusWord) + ?: throw TangemSdkError.DecodingFailed() val decoder = TlvDecoder(tlvData) return SetPinResponse( - cardId = decoder.decode(TlvTag.CardId), - status = status + cardId = decoder.decode(TlvTag.CardId), + status = status ) } } \ 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 b48263e2d1..21c003b64b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -75,8 +75,8 @@ class SignCommand(private val hashes: Array) : Command( 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.Pin, environment.pin1?.value) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) 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 0b563a018e..62c44269e2 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -83,7 +83,7 @@ class WriteIssuerDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) tlvBuilder.append(TlvTag.IssuerData, issuerData) 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 115ebcd1dd..6859aff915 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -149,7 +149,7 @@ class WriteIssuerExtraDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, mode) 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 380f18eaf1..fadb16731d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -57,7 +57,7 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val 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.Pin, environment.pin1?.value) builder.append(TlvTag.UserData, userData) builder.append(TlvTag.UserCounter, userCounter) builder.append(TlvTag.UserProtectedData, userProtectedData) 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 00dccae6a1..912bb15e5f 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 @@ -19,18 +19,6 @@ class DepersonalizeCommand : Command() { override val performPreflightRead = false -// override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { -// if (session.environment.card?.status == CardStatus.NotPersonalized) { -// callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) -// return true -// } -// if (session.environment.card?.firmwareVersion?.contains("SDK") == false) { -// callback(CompletionResult.Failure(TangemSdkError.CannotBeDepersonalized())) -// return true -// } -// return false -// } - override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Depersonalize, byteArrayOf() diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt index 4417374a87..7241e6c4f8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt @@ -3,6 +3,7 @@ package com.tangem.commands.personalization.entities import com.tangem.commands.CardData import com.tangem.commands.EllipticCurve import com.tangem.commands.SigningMethodMask +import com.tangem.common.extensions.calculateSha256 data class NdefRecord( val type: Type, @@ -20,15 +21,15 @@ data class NdefRecord( * It is a configuration file with all the card settings that are written on the card * during [PersonalizeCommand]. */ -data class CardConfig( +class CardConfig( val issuerName: String? = null, val acquirerName: String? = null, val series: String? = null, val startNumber: Long = 0, val count: Int = 0, - val pin: String, - val pin2: String, - val pin3: String, + pin: String, + pin2: String, + pin3: String, val hexCrExKey: String?, val cvc: String, val pauseBeforePin2: Int, @@ -67,5 +68,10 @@ data class CardConfig( val cardData: CardData, val ndefRecords: List ) { + + val pin: ByteArray = pin.calculateSha256() + val pin2: ByteArray = pin2.calculateSha256() + val pin3: ByteArray = pin3.calculateSha256() + companion object } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt index 1eb97c4aa9..bbb3454e66 100644 --- a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt @@ -137,7 +137,7 @@ class VerifyCardCommand(private val onlineVerification: Boolean) : Command { + override val requiresPin2 = false + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.scope.launch { + val pin = this@ChangePinTask.pin + ?: requestNewPin(pinType, session.viewDelegate).calculateSha256() + runSetPin(pin, session, callback) + } + } + + private suspend fun runSetPin(pin: ByteArray, session: CardSession, callback: (result: CompletionResult) -> Unit) { + val pin1: ByteArray + val pin2: ByteArray + val pin3: ByteArray? + + if (session.environment.pin1 == null) { + session.environment.pin1 = PinCode(requestPin(PinType.Pin1, session.viewDelegate)) + } + if (session.environment.pin2 == null) { + session.environment.pin2 = PinCode(requestPin(PinType.Pin2, session.viewDelegate)) + } + + when (pinType) { + PinType.Pin1 -> { + pin1 = pin + pin2 = session.environment.pin2!!.value + pin3 = null + } + PinType.Pin2 -> { + pin1 = session.environment.pin1!!.value + pin2 = pin + pin3 = null + } + PinType.Pin3 -> { + pin1 = session.environment.pin1!!.value + pin2 = session.environment.pin2!!.value + pin3 = pin + } + } + val command = SetPinCommand(pin1, pin2, pin3) + command.run(session) { result -> + when (result) { + is CompletionResult.Success -> { + savePin(pin, session.environment) + callback(result) + } + is CompletionResult.Failure -> callback(result) + } + } + } + + private suspend fun requestPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = + suspendCancellableCoroutine { continuation -> + viewDelegate.onPinRequested(pinType) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + + private suspend fun requestNewPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = + suspendCancellableCoroutine { continuation -> + viewDelegate.onPinChangeRequested(pinType) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + + private fun savePin(pin: ByteArray, environment: SessionEnvironment) { + when (pinType) { + PinType.Pin1 -> environment.pin1 = PinCode(pin, false) + PinType.Pin2 -> environment.pin2 = PinCode(pin, false) + PinType.Pin3 -> {} + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt index 317134af60..a4bb23d8b5 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt @@ -11,7 +11,7 @@ import com.tangem.common.CompletionResult class CreateWalletTask : CardSessionRunnable { - override val performPreflightRead = true + override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val curve = session.environment.card?.curve 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 8b9d8f2452..d9702f5a6d 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -15,41 +15,54 @@ import com.tangem.common.CompletionResult */ internal class ScanTask : CardSessionRunnable { - override val performPreflightRead = false + override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - - if (session.connectedTag == TagType.Slix) { readSlixTag(session, callback) return } - - ReadCommand().run(session) { readResult -> - when(readResult) { - is CompletionResult.Failure -> callback(readResult) - is CompletionResult.Success -> { - val card = readResult.data - session.environment.card = card - if (card.cardData?.productMask?.contains(Product.Tag) != false) { - callback(CompletionResult.Success(card)) - - } else if (card.status != CardStatus.Loaded) { - callback(CompletionResult.Success(card)) - - } else if (card.curve == null || card.walletPublicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - - } else { - val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) - checkWalletCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> callback(CompletionResult.Success(card)) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + if (session.environment.pin1 != null && session.environment.pin2 != null) { + val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) + checkPinCommand.run(session) { result -> + when (result) { + is CompletionResult.Failure -> { + session.environment.pin2 = null } } + runCheckWalletIfNeeded(card, session, callback) + } + } else { + runCheckWalletIfNeeded(card, session, callback) + } + + } + + private fun runCheckWalletIfNeeded( + card: Card, session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (card.cardData?.productMask?.contains(Product.Tag) != false) { + callback(CompletionResult.Success(card)) + + } else if (card.status != CardStatus.Loaded) { + callback(CompletionResult.Success(card)) + + } else if (card.curve == null || card.walletPublicKey == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + + } else { + val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) + checkWalletCommand.run(session) { result -> + when (result) { + is CompletionResult.Success -> callback(CompletionResult.Success(card)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } } } } diff --git a/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq b/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq new file mode 100644 index 0000000000..7ac4521e98 --- /dev/null +++ b/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq @@ -0,0 +1,34 @@ +import com.tangem.VerificationState; + +CREATE TABLE cardValues ( + cardId TEXT NOT NULL UNIQUE PRIMARY KEY, + isPin1Default INTEGER AS Boolean DEFAULT 1, + isPin2Default INTEGER AS Boolean DEFAULT 1, + cardVerification TEXT AS VerificationState, + cardValidation TEXT AS VerificationState, + codeVerification TEXT AS VerificationState +); + +insertOrReplace: +INSERT OR REPLACE INTO cardValues( + cardId, + isPin1Default, + isPin2Default, + cardVerification, + cardValidation, + codeVerification +) +VALUES (?, ?, ?, ?, ?, ?); + +selectByCardId: +SELECT * +FROM cardValues +WHERE cardId = ?; + +deleteAll: +DELETE FROM cardValues; + +deleteByCardId: +DELETE +FROM cardValues +WHERE cardId = ?; \ 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 b995c4de57..67ac0cf109 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 @@ -4,20 +4,17 @@ import com.google.common.truth.Truth.assertThat import com.tangem.SessionEnvironment import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.collect import org.junit.Test class CommandApduTest { + private val sessionEnvironment = SessionEnvironment() + @Test fun `simple READ command to bytes`() { - val sessionEnvironment = SessionEnvironment() val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) val commandApdu = CommandApdu( Instruction.Read, tlvBuilder.serialize() @@ -28,18 +25,17 @@ class CommandApduTest { assertThat(commandApdu.apduData) .isEqualTo(expected) - assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1,2)) + assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1, 2)) } @Test fun `READ with terminal key to bytes`() { - 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, sessionEnvironment.pin1) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey) val commandApdu = CommandApdu( Instruction.Read, diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt b/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt index 5628c01f29..2be072959f 100644 --- a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt +++ b/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt @@ -5,7 +5,6 @@ import android.view.View import android.widget.CompoundButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import com.tangem.SessionEnvironment import com.tangem.TangemSdk import com.tangem.TangemSdkError import com.tangem.common.CompletionResult @@ -103,7 +102,7 @@ class TestUserDataActivity : AppCompatActivity() { chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } - chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } +// chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } } private fun showReadWriteSection(show: Boolean) { @@ -140,8 +139,8 @@ class WriteOptions { userProtectedCounter = if (chbx.isChecked) value else null } - fun updatePin2(chbx: CompoundButton) { - val value = SessionEnvironment.DEFAULT_PIN2 - pin2 = if (chbx.isChecked) value else null - } +// fun updatePin2(chbx: CompoundButton) { +// val value = SessionEnvironment.pin1.DEFAULT_PIN2 +// pin2 = if (chbx.isChecked) value else null +// } } \ No newline at end of file diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index 7b4e3dd92f..bb868f952a 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -62,6 +62,7 @@ dependencies { // misc implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'at.favre.lib:armadillo:0.9.0' + implementation "com.squareup.sqldelight:android-driver:1.4.0" // testing testImplementation 'junit:junit:4.12' diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 3cd49e1741..6367586908 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -1,9 +1,10 @@ package com.tangem.tangem_sdk_new -import androidx.fragment.app.FragmentActivity +import android.app.Activity import com.tangem.* import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog +import com.tangem.tasks.PinType /** * Default implementation of [SessionViewDelegate]. @@ -11,7 +12,7 @@ import com.tangem.tangem_sdk_new.ui.NfcSessionDialog */ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate { - lateinit var activity: FragmentActivity + lateinit var activity: Activity private var readingDialog: NfcSessionDialog? = null init { @@ -19,20 +20,19 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel } override fun onSessionStarted(cardId: String?, message: Message?) { - postUI { showReadingDialog(activity, cardId, message) } + postUI { + if (readingDialog == null) createReadingDialog(activity) + readingDialog?.show(SessionViewDelegateState.Ready(cardId, message)) + } } - private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) { + private fun createReadingDialog(activity: Activity) { val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) readingDialog = NfcSessionDialog(activity) readingDialog?.setContentView(dialogView) readingDialog?.dismissWithAnimation = true readingDialog?.create() - readingDialog?.setOnShowListener { - readingDialog?.show(SessionViewDelegateState.Ready(cardId, message)) - } readingDialog?.setOnCancelListener { reader.stopSession(true) } - readingDialog?.show() } override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) { @@ -68,8 +68,22 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (pin: String?) -> Unit) { - postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } + override fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_enter_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_enter_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_enter_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(message, callback)) } + } + + override fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_change_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_change_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_change_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinChangeRequested(message, callback)) } } private fun setLogger() { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index d5e02a44bb..5552ea2d1a 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -9,7 +9,8 @@ sealed class SessionViewDelegateState() { data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() - data class PinRequested(val callback: (pin: String?) -> Unit) : SessionViewDelegateState() + data class PinRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() + data class PinChangeRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt index e9a6ab555c..7c11dc06c2 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt @@ -1,45 +1,48 @@ package com.tangem.tangem_sdk_new.extensions -import androidx.fragment.app.FragmentActivity +import androidx.activity.ComponentActivity +import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.Config +import com.tangem.Database import com.tangem.SessionViewDelegate import com.tangem.TangemSdk +import com.tangem.common.CardValuesDbStorage import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate import com.tangem.tangem_sdk_new.NfcLifecycleObserver import com.tangem.tangem_sdk_new.TerminalKeysStorage import com.tangem.tangem_sdk_new.nfc.NfcManager -fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config()): TangemSdk { +fun TangemSdk.Companion.init(activity: ComponentActivity, config: Config = Config()): TangemSdk { val nfcManager = TangemSdk.initNfcManager(activity) val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader) viewDelegate.activity = activity - val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config) - tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) - - return tangemSdk + val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") + return TangemSdk( + nfcManager.reader, viewDelegate, config, + CardValuesDbStorage(databaseDriver), TerminalKeysStorage(activity.application) + ) } fun TangemSdk.Companion.customInit( - activity: FragmentActivity, + activity: ComponentActivity, viewDelegate: SessionViewDelegate? = null, config: Config = Config() ): TangemSdk { val nfcManager = TangemSdk.initNfcManager(activity) + val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") - val tangemSdk = TangemSdk( + return TangemSdk( nfcManager.reader, viewDelegate ?: DefaultSessionViewDelegate(nfcManager.reader) .apply { this.activity = activity }, - config + config, + CardValuesDbStorage(databaseDriver), TerminalKeysStorage(activity.application) ) - tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) - - return tangemSdk } -fun TangemSdk.Companion.initNfcManager(activity: FragmentActivity): NfcManager { +fun TangemSdk.Companion.initNfcManager(activity: ComponentActivity): NfcManager { val nfcManager = NfcManager() nfcManager.setCurrentActivity(activity) activity.lifecycle.addObserver(NfcLifecycleObserver(nfcManager)) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index 3ece303dc9..a0ad3666c8 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -8,9 +8,7 @@ import android.view.View import android.view.animation.DecelerateInterpolator import android.view.inputmethod.InputMethodManager import android.widget.TextView -import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog -import com.tangem.Log import com.tangem.tangem_sdk_new.R import com.tangem.tangem_sdk_new.SessionViewDelegateState import com.tangem.tangem_sdk_new.extensions.localizedDescription @@ -19,11 +17,14 @@ import com.tangem.tangem_sdk_new.postUI import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.main.nfc_bottom_sheet.* -class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activity) { +class NfcSessionDialog(val activity: Activity) : BottomSheetDialog(activity) { private var currentState: SessionViewDelegateState? = null fun show(state: SessionViewDelegateState) { + if (!this.isShowing) { + this.show() + } when (state) { is SessionViewDelegateState.Ready -> onReady(state) is SessionViewDelegateState.Success -> onSuccess(state) @@ -31,6 +32,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ is SessionViewDelegateState.SecurityDelay -> onSecurityDelay(state) is SessionViewDelegateState.Delay -> onDelay(state) is SessionViewDelegateState.PinRequested -> onPinRequested(state) + is SessionViewDelegateState.PinChangeRequested -> onPinChangeRequested(state) is SessionViewDelegateState.TagLost -> onTagLost() is SessionViewDelegateState.TagConnected -> onTagConnected() is SessionViewDelegateState.WrongCard -> onWrongCard() @@ -50,9 +52,17 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ tvCardId?.show() tvCardId?.text = cardId } - state.message?.let { message -> - if (message.body != null) tvTaskText?.text = message.body - if (message.header != null) tvTaskTitle?.text = message.header + + if (state.message?.header != null) { + tvTaskTitle?.text = state.message.header + } else { + tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) + } + if (state.message?.body != null) { + tvTaskText?.text = state.message.body + } else { + tvTaskText?.text = activity.getText(R.string.dialog_scan_text) + } } @@ -130,19 +140,76 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ tvTaskText?.visibility = View.INVISIBLE tvTaskTitle?.visibility = View.INVISIBLE show(flPin) + tilPin.hint = state.message performHapticFeedback() etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> postUI { if (actionId == KeyEvent.KEYCODE_ENDCALL) { - show(lTouchCard) - tvTaskTitle?.show() - tvTaskText?.show() - val imm: InputMethodManager = - context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(v?.windowToken, 0) + if (v?.text.isNullOrBlank()) { + etPin?.error = activity.getString(R.string.pin_enter_error_empty) + } else { + show(lTouchCard) + tvTaskTitle?.show() + tvTaskText?.show() + val imm: InputMethodManager = + context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v?.windowToken, 0) - v?.text?.toString()?.let { state.callback(it) } + v?.text.toString().let { pin -> + v?.text = "" + state.callback(pin) + } + } + } + } + true + } + } + + private fun onPinChangeRequested(state: SessionViewDelegateState.PinChangeRequested) { + + tvTaskText?.visibility = View.INVISIBLE + tvTaskTitle?.visibility = View.INVISIBLE + show(llChangePin) + + tilChangePin.hint = state.message + tilChangePinConfirm.hint = activity.getString(R.string.pin_change_confirm) + + performHapticFeedback() + etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> + postUI { + if (actionId == KeyEvent.KEYCODE_ENDCALL) { + if (v?.text.isNullOrBlank()) { + etChangePin?.error = activity.getString(R.string.pin_enter_error_empty) + } + } + } + true + } + + etChangePinConfirm?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> + postUI { + if (actionId == KeyEvent.KEYCODE_ENDCALL) { + if (v?.text.isNullOrBlank()) { + etChangePin?.error = activity.getString(R.string.pin_enter_error_empty) + } else if (v?.text.toString() != etChangePin?.text.toString()) { + etChangePinConfirm?.error = activity.getString(R.string.pin_change_error) + etChangePin?.error = activity.getString(R.string.pin_change_error) + } else { + show(lTouchCard) + tvTaskTitle?.show() + tvTaskText?.show() + val imm: InputMethodManager = + context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v?.windowToken, 0) + + v?.text.toString().let { pin -> + v?.text = "" + etChangePin?.setText("") + state.callback(pin) + } + } } } true @@ -151,7 +218,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ private fun onTagLost() { if (currentState is SessionViewDelegateState.Success || - currentState is SessionViewDelegateState.PinRequested) { + currentState is SessionViewDelegateState.PinRequested) { return } show(lTouchCard) @@ -173,7 +240,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ ) performHapticFeedback() - postUI (2000){ + postUI(2000) { show(lTouchCard) tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) tvTaskText?.text = activity.getText(R.string.dialog_scan_text) @@ -189,6 +256,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ flError?.show(view.id == flError.id) flCompletion?.show(view.id == flCompletion.id) flPin?.show(view.id == flPin.id) + llChangePin?.show(view.id == llChangePin.id) } private fun performHapticFeedback() { diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index d572de2f85..d8afc2b9b7 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -79,6 +79,7 @@ android:layout_width="match_parent" android:layout_height="200dp" android:layout_gravity="center" + android:theme="@style/Theme.MaterialComponents" android:visibility="gone"> + + + + + + + + + + + + + + Ready to Scan Scan card with your phone as shown above Card + Enter New Access Code + Enter New Pass Code + Enter New PIN 3 + Confirm New Code + Codes do not match + Enter code + Enter Access Code + Enter Pass Code + Enter PIN 3