Updated on 2026-08-14

This commit is contained in:
Tangem 2020-07-01 21:27:13 +03:00
parent 834f410d4a
commit f70813e88d
11 changed files with 267 additions and 19 deletions

View file

@ -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<R>)?.requiresPin2 == true && environment.pin2 == null) {
viewDelegate.onSessionStarted(cardId)
viewDelegate.onPinRequested {
viewDelegate.onPinRequested(PinType.Pin2) {
environment.pin2 = PinCode(it)
startWithRunnable(runnable, callback)
}

View file

@ -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)
}
/**

View file

@ -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<SetPinResponse>) -> 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<SetPinResponse>) -> 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<SetPinResponse>) -> 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],

View file

@ -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<SetPinResponse> {
override val performPreflightRead = true
override fun run(session: CardSession, callback: (result: CompletionResult<SetPinResponse>) -> 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<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, 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)
}
}
}

View file

@ -190,7 +190,7 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
callback: (result: CompletionResult<T>) -> 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<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
private fun getPin2FromDelegate(session: CardSession,
callback: (result: CompletionResult<T>) -> Unit) {
session.viewDelegate.onPinRequested { pin2 ->
session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 ->
session.environment.pin2 = PinCode(pin2)
transceive(session, callback)
}

View file

@ -40,6 +40,7 @@ internal class ScanTask : CardSessionRunnable<Card> {
}
}
session.environment.restoreCardValues()
runCheckWalletIfNeeded(card, session, callback)
}
} else {
runCheckWalletIfNeeded(card, session, callback)

View file

@ -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() {

View file

@ -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()

View file

@ -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() {

View file

@ -98,12 +98,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"

View file

@ -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>