Updated on 2026-08-14
This commit is contained in:
parent
834f410d4a
commit
f70813e88d
11 changed files with 267 additions and 19 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ internal class ScanTask : CardSessionRunnable<Card> {
|
|||
}
|
||||
}
|
||||
session.environment.restoreCardValues()
|
||||
runCheckWalletIfNeeded(card, session, callback)
|
||||
}
|
||||
} else {
|
||||
runCheckWalletIfNeeded(card, session, callback)
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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