Updated on 2026-08-14
This commit is contained in:
parent
d7fd830c9c
commit
834f410d4a
32 changed files with 390 additions and 167 deletions
|
|
@ -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,9 +1,11 @@
|
|||
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
|
||||
|
|
@ -29,6 +31,10 @@ interface CardSessionRunnable<T : CommandResponse> {
|
|||
fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit)
|
||||
}
|
||||
|
||||
interface CardSessionStoppedListener {
|
||||
fun onSessionStopped(environment: SessionEnvironment)
|
||||
}
|
||||
|
||||
enum class CardSessionState {
|
||||
Inactive,
|
||||
Active
|
||||
|
|
@ -61,6 +67,7 @@ class CardSession(
|
|||
) {
|
||||
|
||||
var connectedTag: TagType? = null
|
||||
var sessionStoppedListener: CardSessionStoppedListener? = null
|
||||
|
||||
/**
|
||||
* True if some operation is still in progress.
|
||||
|
|
@ -74,7 +81,7 @@ class CardSession(
|
|||
private val tag = this.javaClass.simpleName
|
||||
|
||||
/**
|
||||
* 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,6 +90,24 @@ class CardSession(
|
|||
runnable: T, callback: (result: CompletionResult<R>) -> Unit
|
||||
) {
|
||||
|
||||
if (environment.pin1 == null) {
|
||||
viewDelegate.onSessionStarted(cardId)
|
||||
viewDelegate.onPinRequested {
|
||||
environment.pin1 = PinCode(it)
|
||||
startWithRunnable(runnable, callback)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ((runnable as? Command<R>)?.requiresPin2 == true && environment.pin2 == null) {
|
||||
viewDelegate.onSessionStarted(cardId)
|
||||
viewDelegate.onPinRequested {
|
||||
environment.pin2 = PinCode(it)
|
||||
startWithRunnable(runnable, callback)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
start(runnable.performPreflightRead) { session, error ->
|
||||
if (error != null) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
|
|
@ -209,6 +234,7 @@ class CardSession(
|
|||
}
|
||||
|
||||
private fun stopSession() {
|
||||
sessionStoppedListener?.onSessionStopped(environment)
|
||||
reader.stopSession()
|
||||
state = CardSessionState.Inactive
|
||||
scope.cancel()
|
||||
|
|
@ -259,7 +285,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,14 @@ class Config(
|
|||
*/
|
||||
val cardFilter: CardFilter = CardFilter(),
|
||||
|
||||
var handleErrors: Boolean = true
|
||||
var handleErrors: Boolean = true,
|
||||
|
||||
var defaultPin1: String = "000000",
|
||||
|
||||
var defaultPin2: String = "000",
|
||||
|
||||
var savePin1InStaticField: Boolean = true,
|
||||
|
||||
var savePin2InStaticField: Boolean = false
|
||||
|
||||
)
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem
|
|||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.CardValuesService
|
||||
import com.tangem.common.PinCode
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.crypto.CryptoUtils.generatePublicKey
|
||||
|
||||
|
||||
|
|
@ -13,38 +15,79 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey
|
|||
* @property card Current card, read by preflight [com.tangem.commands.ReadCommand].
|
||||
* @property terminalKeys generated terminal keys used in Linked Terminal feature.
|
||||
*/
|
||||
data class SessionEnvironment(
|
||||
var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(),
|
||||
var card: Card? = null,
|
||||
var terminalKeys: KeyPair? = null,
|
||||
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
var encryptionKey: ByteArray? = null,
|
||||
var cvc: ByteArray? = null,
|
||||
var cardFilter: CardFilter = CardFilter(),
|
||||
val handleErrors: Boolean = true
|
||||
class SessionEnvironment(
|
||||
cardId: String?,
|
||||
private val config: Config,
|
||||
private val terminalKeysService: TerminalKeysService?,
|
||||
private val cardValuesService: CardValuesService?
|
||||
) {
|
||||
|
||||
var pin1: ByteArray = SessionEnvironment.pin1
|
||||
private set
|
||||
get() = SessionEnvironment.pin1
|
||||
var pin1: PinCode?
|
||||
var pin2: PinCode?
|
||||
var cvc: ByteArray? = null
|
||||
|
||||
fun isCurrentPin1Default(): Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256())
|
||||
fun isCurrentPin2Default(): Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256())
|
||||
var terminalKeys: KeyPair? = null
|
||||
var cardFilter: CardFilter
|
||||
val handleErrors: Boolean
|
||||
|
||||
fun setPin1(pin1: String) {
|
||||
SessionEnvironment.pin1 = pin1.calculateSha256()
|
||||
var encryptionMode: EncryptionMode
|
||||
var encryptionKey: ByteArray? = null
|
||||
|
||||
var cardVerification: VerificationState
|
||||
var cardValidation: VerificationState
|
||||
var codeVerification: VerificationState
|
||||
|
||||
var card: Card? = null
|
||||
|
||||
init {
|
||||
terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
|
||||
cardFilter = config.cardFilter
|
||||
handleErrors = config.handleErrors
|
||||
|
||||
encryptionMode = config.encryptionMode
|
||||
|
||||
val cardValues = cardId?.let { cardValuesService?.getValues(cardId) }
|
||||
|
||||
cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified
|
||||
cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified
|
||||
codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified
|
||||
|
||||
pin1 = TangemSdk.pin1
|
||||
?: if (cardValues?.isPin1Default != false) {
|
||||
PinCode(config.defaultPin1, true)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
pin2 = cardId?.let { TangemSdk.pin2[it] }
|
||||
?: if (cardValues?.isPin2Default != false) {
|
||||
PinCode(config.defaultPin2, true)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun setPin2(pin2: String) {
|
||||
this.pin2 = pin2.calculateSha256()
|
||||
fun restoreCardValues() {
|
||||
val cardValues = this.card?.cardId?.let { cardValuesService?.getValues(it) }
|
||||
cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified
|
||||
cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified
|
||||
codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified
|
||||
|
||||
if (cardValues?.isPin1Default == false && pin1?.isDefault == true) pin1 = null
|
||||
if (cardValues?.isPin2Default == false && pin1?.isDefault == true) pin2 = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PIN = "000000"
|
||||
const val DEFAULT_PIN2 = "000"
|
||||
fun saveCardValues() {
|
||||
if (config.savePin1InStaticField) {
|
||||
TangemSdk.pin1 = pin1
|
||||
}
|
||||
if (config.savePin2InStaticField) {
|
||||
card?.cardId?.let { cardId -> TangemSdk.pin2[cardId] = pin2 }
|
||||
}
|
||||
|
||||
var pin1: ByteArray = DEFAULT_PIN.calculateSha256()
|
||||
cardValuesService?.saveValues(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,4 +103,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
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ interface SessionViewDelegate {
|
|||
/**
|
||||
* It is called when a user is expected to enter pin code.
|
||||
*/
|
||||
fun onPinRequested(callback: (pin: String?) -> Unit)
|
||||
fun onPinRequested(callback: (pin: String) -> Unit)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem
|
||||
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.DepersonalizeCommand
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
|
|
@ -10,7 +11,9 @@ 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.CardValuesDbService
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.PinCode
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tasks.CreateWalletTask
|
||||
|
|
@ -30,7 +33,8 @@ import com.tangem.tasks.ScanTask
|
|||
class TangemSdk(
|
||||
private val reader: CardReader,
|
||||
private val viewDelegate: SessionViewDelegate,
|
||||
var config: Config = Config()
|
||||
var config: Config = Config(),
|
||||
private val sqlDriver: SqlDriver
|
||||
) {
|
||||
|
||||
private var terminalKeysService: TerminalKeysService? = null
|
||||
|
|
@ -203,7 +207,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)
|
||||
}
|
||||
|
||||
|
|
@ -378,13 +382,22 @@ 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(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage)
|
||||
addSessionStoppedListener(cardSession)
|
||||
Thread().run { cardSession.startWithRunnable(runnable, callback) }
|
||||
}
|
||||
|
||||
private fun addSessionStoppedListener(session: CardSession) {
|
||||
session.sessionStoppedListener = object : CardSessionStoppedListener {
|
||||
override fun onSessionStopped(environment: SessionEnvironment) {
|
||||
environment.saveCardValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,8 +408,9 @@ 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(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage)
|
||||
addSessionStoppedListener(cardSession)
|
||||
Thread().run { cardSession.start(callback = callback) }
|
||||
}
|
||||
|
||||
|
|
@ -408,14 +422,14 @@ class TangemSdk(
|
|||
this.terminalKeysService = terminalKeysService
|
||||
}
|
||||
|
||||
private fun buildEnvironment(): SessionEnvironment {
|
||||
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
|
||||
private fun buildEnvironment(cardId: String?): SessionEnvironment {
|
||||
return SessionEnvironment(
|
||||
terminalKeys = terminalKeys,
|
||||
cardFilter = config.cardFilter,
|
||||
handleErrors = config.handleErrors
|
||||
cardId, config, terminalKeysService, CardValuesDbService(sqlDriver)
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
companion object {
|
||||
var pin1: PinCode? = null
|
||||
val pin2: MutableMap<String, PinCode?> = mutableMapOf()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,6 +2,7 @@ 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
|
||||
|
|
@ -50,7 +51,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 +58,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) {
|
||||
|
|
@ -74,6 +76,10 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
handlePin1(session, callback)
|
||||
return@transceiveApdu
|
||||
}
|
||||
if (error is TangemSdkError.Pin2OrCvcRequired) {
|
||||
handlePin2(session, callback)
|
||||
return@transceiveApdu
|
||||
}
|
||||
callback(CompletionResult.Failure(error))
|
||||
return@transceiveApdu
|
||||
|
||||
|
|
@ -93,9 +99,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 +120,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 +155,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 +179,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.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 { 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)
|
||||
|
|
|
|||
|
|
@ -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,46 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.squareup.sqldelight.EnumColumnAdapter
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.tangem.CardValues
|
||||
import com.tangem.CardValuesEntityQueries
|
||||
import com.tangem.Database
|
||||
import com.tangem.SessionEnvironment
|
||||
|
||||
interface CardValuesService {
|
||||
|
||||
fun saveValues(environment: SessionEnvironment)
|
||||
|
||||
fun getValues(cardId: String) : CardValues?
|
||||
|
||||
}
|
||||
|
||||
class CardValuesDbService(driver: SqlDriver) : CardValuesService {
|
||||
|
||||
private val cardValuesQueries: CardValuesEntityQueries
|
||||
|
||||
init {
|
||||
val database = Database(driver, cardValuesAdapter = CardValues.Adapter(
|
||||
cardVerificationAdapter = EnumColumnAdapter(),
|
||||
cardValidationAdapter = EnumColumnAdapter(),
|
||||
codeVerificationAdapter = EnumColumnAdapter()
|
||||
))
|
||||
cardValuesQueries = database.cardValuesEntityQueries
|
||||
}
|
||||
|
||||
override fun saveValues(environment: SessionEnvironment) {
|
||||
environment.card?.cardId?.let {cardId ->
|
||||
cardValuesQueries.insertOrReplace(
|
||||
cardId,
|
||||
environment.pin1?.isDefault ?: false,
|
||||
environment.pin2?.isDefault ?: false,
|
||||
environment.cardVerification, environment.cardVerification,
|
||||
environment.codeVerification
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValues(cardId: String): CardValues? =
|
||||
cardValuesQueries.selectByCardId(cardId).executeAsOneOrNull()
|
||||
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
@ -31,24 +31,44 @@ internal class ScanTask : CardSessionRunnable<Card> {
|
|||
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 ->
|
||||
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.Success -> callback(CompletionResult.Success(card))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
is CompletionResult.Failure -> {
|
||||
session.environment.pin2 = null
|
||||
}
|
||||
}
|
||||
session.environment.restoreCardValues()
|
||||
}
|
||||
} 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 = ?;
|
||||
|
|
@ -1,23 +1,21 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.Config
|
||||
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 {
|
||||
|
||||
val sessionEnvironment = SessionEnvironment(null, Config(), null, null)
|
||||
|
||||
@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()
|
||||
|
|
@ -33,13 +31,12 @@ class CommandApduTest {
|
|||
|
||||
@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'
|
||||
|
|
|
|||
|
|
@ -19,20 +19,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: FragmentActivity) {
|
||||
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,7 +67,7 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel
|
|||
postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) }
|
||||
}
|
||||
|
||||
override fun onPinRequested(callback: (pin: String?) -> Unit) {
|
||||
override fun onPinRequested(callback: (pin: String) -> Unit) {
|
||||
postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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 callback: (pin: String) -> Unit) : SessionViewDelegateState()
|
||||
object TagLost : SessionViewDelegateState()
|
||||
object TagConnected : SessionViewDelegateState()
|
||||
object WrongCard : SessionViewDelegateState()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tangem_sdk_new.extensions
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
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.tangem_sdk_new.DefaultSessionViewDelegate
|
||||
|
|
@ -15,7 +17,8 @@ fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config
|
|||
val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader)
|
||||
viewDelegate.activity = activity
|
||||
|
||||
val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config)
|
||||
val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db")
|
||||
val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config, databaseDriver)
|
||||
tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application))
|
||||
|
||||
return tangemSdk
|
||||
|
|
@ -32,7 +35,8 @@ fun TangemSdk.Companion.customInit(
|
|||
nfcManager.reader,
|
||||
viewDelegate ?: DefaultSessionViewDelegate(nfcManager.reader)
|
||||
.apply { this.activity = activity },
|
||||
config
|
||||
config,
|
||||
AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db")
|
||||
)
|
||||
tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application))
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ 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
|
||||
|
|
@ -24,6 +23,9 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ
|
|||
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)
|
||||
|
|
@ -135,14 +137,21 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ
|
|||
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 = ""
|
||||
} 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue