diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index 8047252521..bcaf32f1a7 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -231,6 +231,14 @@ class CardSession( } } + fun pause() { + reader.stopSession() + } + + fun resume() { + reader.startSession() + } + private suspend fun establishEncryptionIfNeeded(): CompletionResult { if (environment.encryptionMode == EncryptionMode.NONE || environment.encryptionKey != null) { return CompletionResult.Success(true) diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 78b6d28715..0534f85d0a 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -14,7 +14,6 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey * @property terminalKeys generated terminal keys used in Linked Terminal feature. */ data class SessionEnvironment( - var pin1: ByteArray = DEFAULT_PIN.calculateSha256(), var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(), var card: Card? = null, var terminalKeys: KeyPair? = null, @@ -25,8 +24,14 @@ data class SessionEnvironment( val handleErrors: Boolean = true ) { + var isDefaultPin1: Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) + + var pin1: ByteArray = SessionEnvironment.pin1 + private set + get() = SessionEnvironment.pin1 + fun setPin1(pin1: String) { - this.pin1 = pin1.calculateSha256() + SessionEnvironment.pin1 = pin1.calculateSha256() } fun setPin2(pin2: String) { @@ -36,6 +41,8 @@ data class SessionEnvironment( companion object { const val DEFAULT_PIN = "000000" const val DEFAULT_PIN2 = "000" + + var pin1: ByteArray = DEFAULT_PIN.calculateSha256() } } diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 71311dcf00..e3b2306bbf 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,7 +1,5 @@ package com.tangem -import com.tangem.common.CompletionResult - /** * Allows interaction with users and shows visual elements. * @@ -49,8 +47,7 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (result: CompletionResult) -> Unit) - + fun onPinRequested(callback: (pin: String?) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 3b25619ce6..57cc3dd0a2 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -64,6 +64,10 @@ abstract class Command : ApduSerializable, CardSessionRu is CompletionResult.Failure -> { if (session.environment.handleErrors) { val error = mapError(session.environment.card, result.error) + if (error is TangemSdkError.Pin1Required) { + handlePin1(session, callback) + return@transceiveApdu + } callback(CompletionResult.Failure(error)) return@transceiveApdu @@ -80,7 +84,6 @@ abstract class Command : ApduSerializable, CardSessionRu } } } - } private fun transceiveApdu( @@ -96,8 +99,10 @@ abstract class Command : ApduSerializable, CardSessionRu val responseApdu = result.data when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, - StatusWord.Pin2Changed, StatusWord.PinsChanged -> { + StatusWord.ProcessCompleted, + StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.Pins12Changed, + StatusWord.Pin3Changed, StatusWord.Pins13Changed, StatusWord.Pins23Changed, + StatusWord.Pins123Changed -> { callback(CompletionResult.Success(responseApdu)) } StatusWord.NeedPause -> { @@ -172,4 +177,26 @@ abstract class Command : ApduSerializable, CardSessionRu return false } + private fun handlePin1( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (!session.environment.isDefaultPin1) { + 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) + } + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 1301703b9f..cb682706bb 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -18,9 +18,8 @@ class OpenSessionResponse( * In case of encrypted communication, App should setup a session before calling any further command. * [OpenSessionCommand] generates secret session_key that is used by both host and card * to encrypt and decrypt commands’ payload. - */ -class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { +class OpenSessionCommand(private val sessionKeyA: ByteArray) : ApduSerializable { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index 743a93c68a..c3765f4ccd 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -43,7 +43,8 @@ class PurgeWalletCommand : Command() { return when (card.status) { CardStatus.Loaded -> null CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() - CardStatus.Empty, CardStatus.Purged -> TangemSdkError.CardIsEmpty() + CardStatus.Empty -> TangemSdkError.CardIsEmpty() + CardStatus.Purged -> TangemSdkError.CardIsPurged() null -> TangemSdkError.CardError() } } diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt index 96daaa2826..0b60f3ff91 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt @@ -5,19 +5,27 @@ import com.tangem.TangemSdkError /** * Part of a response from the card, shows the status of the operation */ -enum class StatusWord(val code: Int, val description: String) { +enum class StatusWord(val code: Int) { - ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"), - InvalidParams(0x6A86, "SW_INVALID_PARAMS"), - ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"), - InvalidState(0x6985, "SW_INVALID_STATE"), - Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"), - Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"), - PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"), - InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"), - NeedEncryption(0x6982, "SW_NEED_ENCRYPTION"), - NeedPause(0x9789, "SW_NEED_PAUSE"), - Unknown(0x0000, "SW_UNKNOWN"); + ProcessCompleted(0x9000), + InvalidParams(0x6A86), + ErrorProcessingCommand(0x6286), + InvalidState(0x6985), + + //PinsNotChanged(0x9000) is equal to ProcessCompleted(0x9000) + Pin1Changed(0x9001), + Pin2Changed(0x9002), + Pins12Changed(0x9003), + Pin3Changed(0x9004), + Pins13Changed(0x9005), + Pins23Changed(0x9006), + Pins123Changed(0x9007), + + + InsNotSupported(0x6D00), + NeedEncryption(0x6982), + NeedPause(0x9789), + Unknown(0x0000); companion object { private val values = values() @@ -28,7 +36,8 @@ enum class StatusWord(val code: Int, val description: String) { fun StatusWord.toTangemSdkError(): TangemSdkError? { return when (this) { StatusWord.ProcessCompleted, StatusWord.Pin1Changed, - StatusWord.Pin2Changed, StatusWord.PinsChanged -> null + StatusWord.Pin2Changed, StatusWord.Pins12Changed, StatusWord.Pin3Changed, + StatusWord.Pins13Changed, StatusWord.Pins23Changed, StatusWord.Pins123Changed -> null StatusWord.NeedPause -> null StatusWord.InvalidParams -> TangemSdkError.InvalidParams() StatusWord.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand() diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index e3c9122c59..944ee4cc24 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -125,7 +125,6 @@ enum class TlvTag(val code: Int) { fun valueType(): TlvValueType { return when (this) { CardId, Batch, CrExKey -> TlvValueType.HexString - NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 31144eae96..3cd49e1741 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -2,7 +2,6 @@ package com.tangem.tangem_sdk_new import androidx.fragment.app.FragmentActivity import com.tangem.* -import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog @@ -69,7 +68,7 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (result: CompletionResult) -> Unit) { + override fun onPinRequested(callback: (pin: String?) -> Unit) { postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index 04ecbd2557..d5e02a44bb 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -2,7 +2,6 @@ package com.tangem.tangem_sdk_new import com.tangem.Message import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult sealed class SessionViewDelegateState() { data class Error(val error: TangemSdkError) : SessionViewDelegateState() @@ -10,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: (result: CompletionResult) -> Unit) : SessionViewDelegateState() + data class PinRequested(val callback: (pin: String?) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index 4e7468f390..3ece303dc9 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -1,11 +1,16 @@ package com.tangem.tangem_sdk_new.ui import android.animation.ObjectAnimator +import android.app.Activity import android.view.HapticFeedbackConstants +import android.view.KeyEvent 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 @@ -121,11 +126,34 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ } private fun onPinRequested(state: SessionViewDelegateState.PinRequested) { - TODO("To be implemented") + + tvTaskText?.visibility = View.INVISIBLE + tvTaskTitle?.visibility = View.INVISIBLE + show(flPin) + + 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) + + v?.text?.toString()?.let { state.callback(it) } + } + } + true + } } private fun onTagLost() { - if (currentState is SessionViewDelegateState.Success) return + if (currentState is SessionViewDelegateState.Success || + currentState is SessionViewDelegateState.PinRequested) { + return + } show(lTouchCard) tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) tvTaskText?.text = activity.getText(R.string.dialog_scan_text) @@ -160,6 +188,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ flReading?.show(view.id == flReading.id) flError?.show(view.id == flError.id) flCompletion?.show(view.id == flCompletion.id) + flPin?.show(view.id == flPin.id) } private fun performHapticFeedback() { diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index 55317c459a..d572de2f85 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -57,7 +57,6 @@ - + + + + + + + + + +