diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index 4238ce7832..8c67cffd39 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,9 +1,6 @@ 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.commands.* import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.apdu.CommandApdu @@ -92,7 +89,7 @@ class CardSession( if (environment.pin1 == null) { viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested { + viewDelegate.onPinRequested(PinType.Pin1) { environment.pin1 = PinCode(it) startWithRunnable(runnable, callback) } @@ -101,7 +98,7 @@ class CardSession( if ((runnable as? Command)?.requiresPin2 == true && environment.pin2 == null) { viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested { + viewDelegate.onPinRequested(PinType.Pin2) { environment.pin2 = PinCode(it) startWithRunnable(runnable, callback) } diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 7177c0a2b6..3405c7c676 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,5 +1,7 @@ package com.tangem +import com.tangem.commands.PinType + /** * Allows interaction with users and shows visual elements. * @@ -47,7 +49,12 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (pin: String) -> Unit) + fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) + + /** + * It is called when a user wants to change pin code. + */ + fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 190c13f515..266301b737 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -365,6 +365,30 @@ class TangemSdk( startSessionWithRunnable(command, null, initialMessage, callback) } + fun changePin1(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(PinType.Pin1, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin2(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(PinType.Pin2, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin3(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(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], diff --git a/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt new file mode 100644 index 0000000000..641fb6e3d2 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt @@ -0,0 +1,82 @@ +package com.tangem.commands + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionViewDelegate +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 ChangePinCommand( + private val pinType: PinType, + private val pin: ByteArray? = null +) : CardSessionRunnable { + override val performPreflightRead = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.scope.launch { + val pin = this@ChangePinCommand.pin + ?: requestNewPin(pinType, session.viewDelegate).calculateSha256() + runSetPin(pin, session, callback) + } + } + + private suspend fun runSetPin(pin: ByteArray, session: CardSession, callback: (result: CompletionResult) -> Unit) { + val pin1: ByteArray + val pin2: ByteArray + val pin3: ByteArray? + + if (session.environment.pin1 == null) { + session.environment.pin1 = PinCode(requestPin(PinType.Pin1, session.viewDelegate)) + } + if (session.environment.pin2 == null) { + session.environment.pin2 = PinCode(requestPin(PinType.Pin2, session.viewDelegate)) + } + + when (pinType) { + PinType.Pin1 -> { + pin1 = pin + pin2 = session.environment.pin2!!.value + pin3 = null + } + PinType.Pin2 -> { + pin1 = session.environment.pin1!!.value + pin2 = pin + pin3 = null + } + PinType.Pin3 -> { + pin1 = session.environment.pin1!!.value + pin2 = session.environment.pin2!!.value + pin3 = pin + } + } + val command = SetPinCommand(pin1, pin2, pin3) + command.run(session, callback) + } + + 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) + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index cad5effb77..27abf365a7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -190,7 +190,7 @@ abstract class Command : ApduSerializable, CardSessionRu callback: (result: CompletionResult) -> Unit ) { session.pause() - session.viewDelegate.onPinRequested { pin1 -> + session.viewDelegate.onPinRequested(PinType.Pin1) { pin1 -> session.environment.pin1 = PinCode(pin1) session.resume() transceive(session, callback) @@ -226,7 +226,7 @@ abstract class Command : ApduSerializable, CardSessionRu private fun getPin2FromDelegate(session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.viewDelegate.onPinRequested { pin2 -> + session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 -> session.environment.pin2 = PinCode(pin2) transceive(session, callback) } diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index d957d2966c..512171113f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -40,6 +40,7 @@ internal class ScanTask : CardSessionRunnable { } } session.environment.restoreCardValues() + runCheckWalletIfNeeded(card, session, callback) } } else { runCheckWalletIfNeeded(card, session, callback) 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 9308dffddc..b2bbd8afe0 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,6 +2,7 @@ package com.tangem.tangem_sdk_new import androidx.fragment.app.FragmentActivity import com.tangem.* +import com.tangem.commands.PinType import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog @@ -67,8 +68,22 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (pin: String) -> Unit) { - postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } + override fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_enter_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_enter_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_enter_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(message, callback)) } + } + + override fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_change_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_change_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_change_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinChangeRequested(message, callback)) } } private fun setLogger() { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index 1880e23a17..5552ea2d1a 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -9,7 +9,8 @@ sealed class SessionViewDelegateState() { data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() - data class PinRequested(val callback: (pin: String) -> Unit) : SessionViewDelegateState() + data class PinRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() + data class PinChangeRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index e1ffec7f98..5b32b7e0a2 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 @@ -33,6 +33,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() @@ -52,9 +53,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) + } } @@ -132,13 +141,14 @@ 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) { if (v?.text.isNullOrBlank()) { - etPin?.error = "" + etPin?.error = activity.getString(R.string.pin_enter_error_empty) } else { show(lTouchCard) tvTaskTitle?.show() @@ -158,9 +168,58 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ } } + 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 + } + } + private fun onTagLost() { if (currentState is SessionViewDelegateState.Success || - currentState is SessionViewDelegateState.PinRequested) { + currentState is SessionViewDelegateState.PinRequested) { return } show(lTouchCard) @@ -182,7 +241,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) @@ -198,6 +257,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ flError?.show(view.id == flError.id) flCompletion?.show(view.id == flCompletion.id) flPin?.show(view.id == flPin.id) + llChangePin?.show(view.id == llChangePin.id) } private fun performHapticFeedback() { diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index d572de2f85..3ded5704da 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -98,12 +98,64 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:backgroundTint="#f3f5f6" - android:hint="Enter PIN" android:inputType="textPassword" /> + + + + + + + + + + + + + + Ready to Scan Scan card with your phone as shown above Card + Enter New Access Code + Enter New Pass Code + Enter New PIN 3 + Confirm New Code + Codes do not match + Enter code + Enter Access Code + Enter Pass Code + Enter PIN 3