Updated on 2026-08-14
This commit is contained in:
commit
db06852c97
40 changed files with 771 additions and 258 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T : CommandResponse> {
|
||||
|
||||
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<R>) -> 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
|
||||
|
|
|
|||
|
|
@ -36,6 +36,18 @@ class Config(
|
|||
*/
|
||||
val cardFilter: CardFilter = CardFilter(),
|
||||
|
||||
var handleErrors: Boolean = true
|
||||
var handleErrors: Boolean = true,
|
||||
|
||||
)
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<WriteUserDataResponse>) -> 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<SetPinResponse>) -> 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<SetPinResponse>) -> 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<SetPinResponse>) -> 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 <T : CommandResponse> startSessionWithRunnable(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
|
||||
callback: (result: CompletionResult<T>) -> 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<String, PinCode?> = 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
|
||||
}
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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<T : CommandResponse> {
|
||||
|
|
@ -37,8 +39,8 @@ interface CommandResponse
|
|||
*/
|
||||
abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRunnable<T> {
|
||||
|
||||
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<T>) -> Unit) {
|
||||
Log.i("Command", "Initializing ${this::class.java.simpleName}")
|
||||
|
|
@ -50,7 +52,6 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error
|
||||
|
||||
fun transceive(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
|
||||
|
||||
val card = session.environment.card
|
||||
if (session.environment.handleErrors && card != null) {
|
||||
performPreCheck(card)?.let { error ->
|
||||
|
|
@ -58,12 +59,14 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, 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<T>) -> Unit) {
|
||||
val apdu = serialize(session.environment)
|
||||
transceiveApdu(apdu, session) { result ->
|
||||
when (result) {
|
||||
|
|
@ -71,9 +74,15 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, 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<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
}
|
||||
|
||||
private fun transceiveApdu(
|
||||
apdu: CommandApdu,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ResponseApdu>) -> Unit
|
||||
apdu: CommandApdu,
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ResponseApdu>) -> Unit
|
||||
) {
|
||||
Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}")
|
||||
|
||||
|
|
@ -114,17 +123,17 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, 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<T : CommandResponse> : ApduSerializable<T>, 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<T : CommandResponse> : ApduSerializable<T>, 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<T>) -> Unit
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<T>) -> 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<T>) -> Unit
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<T>) -> 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<T>) -> Unit) {
|
||||
session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 ->
|
||||
session.environment.pin2 = PinCode(pin2)
|
||||
transceive(session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,9 +62,9 @@ class CreateWalletCommand : Command<CreateWalletResponse>() {
|
|||
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ class PurgeWalletCommand : Command<PurgeWalletResponse>() {
|
|||
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ class ReadCommand : Command<Card>() {
|
|||
* 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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class ReadUserDataCommand : Command<ReadUserDataResponse>() {
|
|||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SetPinResponse>() {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,8 +75,8 @@ class SignCommand(private val hashes: Array<ByteArray>) : Command<SignResponse>(
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -19,18 +19,6 @@ class DepersonalizeCommand : Command<DepersonalizeResponse>() {
|
|||
|
||||
override val performPreflightRead = false
|
||||
|
||||
// override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<DepersonalizeResponse>) -> 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()
|
||||
|
|
|
|||
|
|
@ -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<NdefRecord>
|
||||
) {
|
||||
|
||||
val pin: ByteArray = pin.calculateSha256()
|
||||
val pin2: ByteArray = pin2.calculateSha256()
|
||||
val pin3: ByteArray = pin3.calculateSha256()
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ class VerifyCardCommand(private val onlineVerification: Boolean) : Command<Verif
|
|||
|
||||
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.VerifyCard, tlvBuilder.serialize())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.squareup.sqldelight.EnumColumnAdapter
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
|
||||
import com.tangem.CardValues
|
||||
import com.tangem.CardValuesEntityQueries
|
||||
import com.tangem.Database
|
||||
import com.tangem.VerificationState
|
||||
|
||||
interface CardValuesStorage {
|
||||
|
||||
fun saveValues(cardId: String,
|
||||
isPin1Default: Boolean, isPin2Default: Boolean,
|
||||
cardVerification: VerificationState?,
|
||||
cardValidation: VerificationState?,
|
||||
codeVerification: VerificationState?)
|
||||
|
||||
fun getValues(cardId: String): CardValues?
|
||||
|
||||
}
|
||||
|
||||
class CardValuesDbStorage(driver: SqlDriver) : CardValuesStorage {
|
||||
|
||||
private val cardValuesQueries: CardValuesEntityQueries
|
||||
|
||||
init {
|
||||
val database = Database(driver, cardValuesAdapter = CardValues.Adapter(
|
||||
cardVerificationAdapter = EnumColumnAdapter(),
|
||||
cardValidationAdapter = EnumColumnAdapter(),
|
||||
codeVerificationAdapter = EnumColumnAdapter()
|
||||
))
|
||||
cardValuesQueries = database.cardValuesEntityQueries
|
||||
}
|
||||
|
||||
override fun saveValues(cardId: String,
|
||||
isPin1Default: Boolean, isPin2Default: Boolean,
|
||||
cardVerification: VerificationState?,
|
||||
cardValidation: VerificationState?,
|
||||
codeVerification: VerificationState?) {
|
||||
cardValuesQueries.insertOrReplace(
|
||||
cardId,
|
||||
isPin1Default, isPin2Default,
|
||||
cardVerification, cardValidation, codeVerification
|
||||
)
|
||||
}
|
||||
|
||||
override fun getValues(cardId: String): CardValues? =
|
||||
cardValuesQueries.selectByCardId(cardId).executeAsOneOrNull()
|
||||
|
||||
companion object {
|
||||
fun initJvm() = CardValuesDbStorage(
|
||||
JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).also { Database.Schema.create(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
10
tangem-core/src/main/java/com/tangem/common/PinCode.kt
Normal file
10
tangem-core/src/main/java/com/tangem/common/PinCode.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
|
||||
class PinCode(
|
||||
val value: ByteArray,
|
||||
val isDefault: Boolean
|
||||
) {
|
||||
constructor(value: String, isDefault: Boolean = false): this(value.calculateSha256(), isDefault)
|
||||
}
|
||||
101
tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt
Normal file
101
tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.SessionViewDelegate
|
||||
import com.tangem.commands.SetPinCommand
|
||||
import com.tangem.commands.SetPinResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.PinCode
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
enum class PinType {
|
||||
Pin1,
|
||||
Pin2,
|
||||
Pin3,
|
||||
;
|
||||
}
|
||||
|
||||
class ChangePinTask(
|
||||
private val pinType: PinType,
|
||||
private val pin: ByteArray? = null
|
||||
) : CardSessionRunnable<SetPinResponse> {
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<SetPinResponse>) -> 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<SetPinResponse>) -> 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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.common.CompletionResult
|
|||
|
||||
class CreateWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
||||
|
||||
override val performPreflightRead = true
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
val curve = session.environment.card?.curve
|
||||
|
|
|
|||
|
|
@ -15,41 +15,54 @@ import com.tangem.common.CompletionResult
|
|||
*/
|
||||
internal class ScanTask : CardSessionRunnable<Card> {
|
||||
|
||||
override val performPreflightRead = false
|
||||
override val requiresPin2 = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> 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<Card>) -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ?;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// }
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
|
@ -98,12 +99,64 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:hint="Enter PIN"
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llChangePin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilChangePin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etChangePin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:hint="Enter PIN"
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilChangePinConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etChangePinConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:hint="Enter PIN"
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flSecurityDelay"
|
||||
|
|
|
|||
|
|
@ -10,5 +10,14 @@
|
|||
<string name="dialog_ready_to_scan">Ready to Scan</string>
|
||||
<string name="dialog_scan_text">Scan card with your phone as shown above</string>
|
||||
<string name="header_card">Card</string>
|
||||
<string name="pin_change_pin_1">Enter New Access Code</string>
|
||||
<string name="pin_change_pin_2">Enter New Pass Code</string>
|
||||
<string name="pin_change_pin_3">Enter New PIN 3</string>
|
||||
<string name="pin_change_confirm">Confirm New Code</string>
|
||||
<string name="pin_change_error">Codes do not match</string>
|
||||
<string name="pin_enter_error_empty">Enter code</string>
|
||||
<string name="pin_enter_pin_1">Enter Access Code</string>
|
||||
<string name="pin_enter_pin_2">Enter Pass Code</string>
|
||||
<string name="pin_enter_pin_3">Enter PIN 3</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue