Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-15 16:58:19 +03:00
parent 9bfdafd678
commit f0ba8cca13
8 changed files with 134 additions and 95 deletions

View file

@ -20,7 +20,7 @@ class ResponseJsonConverter {
private fun init(): Gson {
val builder = GsonBuilder().apply {
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter(fieldConverter))
registerTypeAdapter(SigningMethod::class.java, SigningMethodTypeAdapter(fieldConverter))
registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter(fieldConverter))
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter(fieldConverter))
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter(fieldConverter))
registerTypeAdapter(Date::class.java, DateTypeAdapter())
@ -62,8 +62,8 @@ class ProductMaskTypeAdapter(
class SigningMethodTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<SigningMethod> {
override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
) : JsonSerializer<SigningMethodMask> {
override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonArray().apply {
fieldConverter.signingMethodList(src).forEach { add(it) }
}
@ -86,32 +86,17 @@ class ResponseFieldConverter {
fun productMaskList(productMask: ProductMask?): List<String> {
val mask = productMask ?: return emptyList()
val signingMap = mutableMapOf(
ProductMask.note to "Note",
ProductMask.tag to "Tag",
ProductMask.idCard to "IdCard",
ProductMask.idIssuer to "IdIssuer"
)
return signingMap.filter { mask.contains(it.key) }.map { it.value }
return Product.values().filter { mask.contains(it) }.map { it.name }
}
fun signingMethod(signingMethod: SigningMethod?): String {
return signingMethodList(signingMethod).print(wrap = false)
fun signingMethod(signingMask: SigningMethodMask?): String {
return signingMethodList(signingMask).print(wrap = false)
}
fun signingMethodList(signingMethod: SigningMethod?): List<String> {
val methods = signingMethod ?: return emptyList()
fun signingMethodList(signingMask: SigningMethodMask?): List<String> {
val mask = signingMask ?: return emptyList()
val signingMap = mutableMapOf(
SigningMethod.signHash to "Hash",
SigningMethod.signRaw to "Raw",
SigningMethod.signHashValidatedByIssuer to "HashValidatedByIssuer",
SigningMethod.signRawValidatedByIssuer to "RawValidatedByIssuer",
SigningMethod.signHashValidatedByIssuerAndWriteIssuerData to "HashValidatedByIssuerAndWriteIssuerData",
SigningMethod.signRawValidatedByIssuerAndWriteIssuerData to "RawValidatedByIssuerAndWriteIssuerData",
SigningMethod.signPos to "Pos"
)
return signingMap.filter { methods.contains(it.key) }.map { it.value }
return SigningMethod.values().filter { mask.contains(it) }.map { it.name }
}
fun settingsMask(settingsMask: SettingsMask?): String {
@ -124,7 +109,7 @@ class ResponseFieldConverter {
return Settings.values().filter { masks.contains(it) }.map { it.name }
}
fun byteArray(byteArray: ByteArray?): String {
return byteArray?.toHexString() ?: ""
fun byteArray(byteArray: ByteArray?): String? {
return byteArray?.toHexString()
}
}

View file

@ -30,7 +30,7 @@ class ResponseResources {
holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key))
holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve))
holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures))
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.response_card_signing_method))
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.info_response_card_signing_method))
holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2))
holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key))
holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures))

View file

@ -1,33 +1,27 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.CardData
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.commands.*
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.domain.responses.ResponseFieldConverter
import com.tangem.tangemtest.ucase.variants.personalize.BlockId
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
import com.tangem.tangemtest.ucase.variants.responses.CardId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
*/
class CardConverter : ModelToItems<Card> {
private val fieldConverter = ResponseFieldConverter()
class CardConverter : BaseResponseConverter<Card>() {
override fun convert(from: Card): List<Item> {
val itemList = mutableListOf<Item>()
itemList.add(simpleFields(from))
itemList.add(cardData(from.cardData))
itemList.add(settingsMask(from.settingsMask))
commonGroup(itemList, from)
cardDataGroup(itemList, from.cardData)
settingsMaskGroup(itemList, from.settingsMask)
hideEmptyNullFields(itemList)
return itemList
@ -35,70 +29,66 @@ class CardConverter : ModelToItems<Card> {
private fun hideEmptyNullFields(itemList: MutableList<Item>) {
itemList.iterate {
val data = it.getData<Any?>()
val isHidden = if (data == null) true
else when (data) {
is String -> data.isEmpty()
else -> false
}
if (it is ItemGroup) return@iterate
if (isHidden) it.viewModel.viewState.isVisibleState.value = false
if (it.getData<Any?>() == null) it.viewModel.viewState.isVisibleState.value = false
}
}
private fun simpleFields(from: Card): Item {
val group = createGroup(BlockId.Common)
private fun commonGroup(itemList: MutableList<Item>, from: Card) {
val group = createGroup(CardId.empty, addHeaderItem = false)
itemList.add(group)
group.addItem(TextItem(CardId.cardId, from.cardId))
group.addItem(TextItem(CardId.manufacturerName, from.manufacturerName))
group.addItem(TextItem(CardId.status, stringOf(from.status)))
group.addItem(TextItem(CardId.status, valueToString(from.status)))
group.addItem(TextItem(CardId.firmwareVersion, from.firmwareVersion))
group.addItem(TextItem(CardId.cardPublicKey, fieldConverter.byteArray(from.cardPublicKey)))
group.addItem(TextItem(CardId.issuerPublicKey, fieldConverter.byteArray(from.issuerPublicKey)))
group.addItem(TextItem(CardId.curve, stringOf(from.curve)))
group.addItem(TextItem(CardId.maxSignatures, stringOf(from.maxSignatures)))
group.addItem(TextItem(CardId.signingMethod, fieldConverter.signingMethod(from.signingMethod)))
group.addItem(TextItem(CardId.pauseBeforePin2, stringOf(from.pauseBeforePin2)))
group.addItem(TextItem(CardId.curve, valueToString(from.curve)))
group.addItem(TextItem(CardId.maxSignatures, valueToString(from.maxSignatures)))
group.addItem(TextItem(CardId.pauseBeforePin2, valueToString(from.pauseBeforePin2)))
group.addItem(TextItem(CardId.walletPublicKey, fieldConverter.byteArray(from.walletPublicKey)))
group.addItem(TextItem(CardId.walletRemainingSignatures, stringOf(from.walletRemainingSignatures)))
group.addItem(TextItem(CardId.walletSignedHashes, stringOf(from.walletSignedHashes)))
group.addItem(TextItem(CardId.health, stringOf(from.health)))
group.addItem(TextItem(CardId.isActivated, stringOf(from.isActivated)))
group.addItem(TextItem(CardId.activationSeed, stringOf(from.activationSeed)))
group.addItem(TextItem(CardId.paymentFlowVersion, stringOf(from.paymentFlowVersion)))
group.addItem(TextItem(CardId.userCounter, stringOf(from.userCounter)))
// block.addItem(TextItem(CardId.UserProtectedCounter, stringOf(from.userProtectedCounter)))
return group
group.addItem(TextItem(CardId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)))
group.addItem(TextItem(CardId.walletSignedHashes, valueToString(from.walletSignedHashes)))
group.addItem(TextItem(CardId.health, valueToString(from.health)))
group.addItem(TextItem(CardId.isActivated, valueToString(from.isActivated)))
group.addItem(TextItem(CardId.activationSeed, valueToString(from.activationSeed)))
group.addItem(TextItem(CardId.paymentFlowVersion, valueToString(from.paymentFlowVersion)))
group.addItem(TextItem(CardId.userCounter, valueToString(from.userCounter)))
val settingsMask = from.settingsMask ?: return
group.addItem(TextHeaderItem(CardId.signingMethod, ""))
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), settingsMask.contains(it))) }
}
private fun cardData(from: CardData?): Item {
val group = createGroup(BlockId.Common, R.color.group_card_data)
val data = from ?: return group
private fun cardDataGroup(itemList: MutableList<Item>, cardData: CardData?) {
val data = cardData ?: return
// group.addItem(TextItem(StringResId(R.string.response_card_card_data)))
val group = createGroup(CardId.cardData, R.color.group_card_data)
itemList.add(group)
group.addItem(TextItem(CardDataId.batchId, data.batchId))
// Format: Year (2 bytes) | Month (1 byte) | Day (1 byte)
group.addItem(TextItem(CardDataId.manufactureDateTime, stringOf(data.manufactureDateTime)))
group.addItem(TextItem(CardDataId.manufactureDateTime, valueToString(data.manufactureDateTime)))
group.addItem(TextItem(CardDataId.issuerName, data.issuerName))
group.addItem(TextItem(CardDataId.blockchainName, data.blockchainName))
group.addItem(TextItem(CardDataId.manufacturerSignature, fieldConverter.byteArray(data.manufacturerSignature)))
group.addItem(TextItem(CardDataId.productMask, fieldConverter.productMask(data.productMask)))
group.addItem(TextItem(CardDataId.tokenSymbol, data.tokenSymbol))
group.addItem(TextItem(CardDataId.tokenContractAddress, data.tokenContractAddress))
group.addItem(TextItem(CardDataId.tokenDecimal, data.tokenSymbol))
return group
val productMask = data.productMask ?: return
group.addItem(TextHeaderItem(CardDataId.productMask, ""))
Product.values().forEach { group.addItem(BoolItem(StringId(it.name), productMask.contains(it))) }
}
private fun settingsMask(from: SettingsMask?): Item {
private fun settingsMaskGroup(itemList: MutableList<Item>, from: SettingsMask?) {
val data = from ?: return
val group = createGroup(CardId.settingsMask, R.color.group_settings_mask)
val data = from ?: return group
itemList.add(group)
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), data.contains(it))) }
return group
}
private fun createGroup(id: Id, colorId: Int? = null): ItemGroup {
return if (colorId == null) SimpleItemGroup(id)
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tangemtest.ucase.variants.responses.item
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
import com.tangem.tangemtest._arch.structure.impl.TypedItem
open class TextHeaderItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}

View file

@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest._arch.widget.ItemWidgetBuilder
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -13,6 +14,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
class ResponseItemBuilder : ItemWidgetBuilder {
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
return when (item) {
is TextHeaderItem -> ResponseHeaderWidget(parent, item)
is TextItem -> ResponseTextWidget(parent, item)
is BoolItem -> CheckBoxWidget(parent, item)
else -> null

View file

@ -4,6 +4,7 @@ import android.view.ViewGroup
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -27,4 +28,18 @@ class ResponseTextWidget(
tvName.text = getName()
tvValue.text = data
}
}
class ResponseHeaderWidget(
parent: ViewGroup,
private val typedItem: TextHeaderItem
) : ResponseWidget(parent, typedItem) {
override fun getLayoutId(): Int = R.layout.w_response_item_header
private val tvName: TextView = view.findViewById(R.id.tv_name)
init {
tvName.text = getName()
}
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/field_header_background">
<LinearLayout
android:id="@+id/container_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/def_indent"
android:paddingBottom="8dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/field_content"
android:textSize="16sp"
tools:text="Custom blockchain" />
<include layout="@layout/w_field_description" />
</LinearLayout>
<include
layout="@layout/m_divider_h"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom" />
</FrameLayout>

View file

@ -57,7 +57,7 @@
<string name="info_response_card_status">Current status of the card</string>
<string name="info_response_card_firmware_version">Version of Tangem COS</string>
<string name="info_response_card_public_key">Public key that is used to authenticate the card against manufacturers database. It is generated one time during card manufacturing. See Security section for more details.</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization (bit mask: 0 Enabled, 1 Disabled):</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization.</string>
<string name="info_response_card_is_reusable">Defines what happens when user calls PURGE_WALLET command:
\n0 - Card will switch to Purged state
\n1 - Card will switch to Empty state and let create a new wallet again</string>
@ -72,29 +72,29 @@
<string name="info_response_card_one_apdu_at_time">Card will execute only one command during one communication session, thus requiring user to physically take the card away from the host after each action (all commands except for READ_CARD).</string>
<string name="info_response_card_use_ndef">Whether the card should emulate NDEF. In default configuration, two NDEF records are loaded during personalization: (1) Tangem web site address, (2) name of Android App package in Google Play Store.</string>
<string name="info_response_card_use_dynamic_ndef">0 Disable dynamic generation of NDEF for iOS. See Dynamic NDEF section for more details.
/n1 Enable dynamic NDEF for iOS.</string>
\n1 Enable dynamic NDEF for iOS.</string>
<string name="info_response_card_smart_security_delay">Security delay Pause_Before_PIN2 will not be applied if PIN2 is not default.</string>
<string name="info_response_card_allow_unencrypted">Whether the card supports unencrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_allow_fast_encryption">Whether the card supports fast encrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_protect_issuer_data_against_replay">0 No replay protection on write issuer data
/n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
\n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
<string name="info_response_card_allow_select_blockchain">0 Wallet elliptic curve and blockchain information stored during PERSONALIZE command and never change
/n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
\n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
<string name="info_response_card_disable_precomputed_ndef">0 Enable precomputed dynamic NDEF to work around iPhone 7+ NFC bug.
/n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
\n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
<string name="info_response_card_security_delay_if_validated">0 Enforce security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).
/n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
\n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_pin2_cvc_if_validated_by_issuer">0 Require and check PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).
/n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
\n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_security_delay_if_validated_by_linked_terminal">1 - Store Terminal_PublicKey public key of linked terminal no each SIGN command, skip security delay if valid signature of transaction is made with Terminal_PrivateKey is provided in SIGN command</string>
<string name="info_response_card_restrict_overwrite_issuer_ex_data"></string>
<string name="info_info_response_card_prohibit_overwriting_issuer_ex_data"></string>
<string name="info_response_card_require_terminal_tx_sig">0 Skip checking terminals signature when signing POS transaction
/n1 Check terminals signature when signing POS transaction</string>
\n1 Check terminals signature when signing POS transaction</string>
<string name="info_response_card_require_terminal_cert_sig">0 Skip checking acquirers signature of terminal certificate when signing POS transaction
/n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
\n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
<string name="info_response_card_check_pin3">0 Additionally encrypt POS transaction signature with key derived from PIN3 when the transaction amount exceeds PIN3 floor limit
/n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
\n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
<string name="info_response_card_card_data">Detailed information about card contents. Format is defined by the card issuer. Cards complaint with Tangem Wallet application should have TLV format described in Personalization section.</string>
<string name="info_response_card_issuer_data_public_key">Public key that is used by the card issuer to sign Issuer_Data field. See Security section for more details.</string>
<string name="info_response_card_curve">Explicit text name of the elliptic curve used for all wallet key operations.</string>
@ -106,12 +106,12 @@
<string name="info_response_card_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
<string name="info_response_card_health">Any non-zero value indicates that the card experiences some hardware problems. User should withdraw the value to other blockchain wallet as soon as possible. Non-zero Health tag will also appear in responses of all other commands.</string>
<string name="info_response_card_is_activated">Whether the card requires issuers confirmation of activation.
/n0 card will require issuers confirmation of activation,
/notherwise this field will not be returned (card is activated and operational).</string>
\n0 card will require issuers confirmation of activation,
\notherwise this field will not be returned (card is activated and operational).</string>
<string name="info_response_card_activation_seed">A random challenge generated by PERSONALIZE command that should be signed and returned to COS by the issuer to confirm the card has been activated. See ACTIVATE_CARD command for more details.
/nThis field will not be returned if the card is activated.</string>
\nThis field will not be returned if the card is activated.</string>
<string name="info_response_card_payment_flow_version">Version of POS payment scheme supported by COS ([0x02,0x01] for version 2.30)
/nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
\nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
<string name="info_response_card_user_counter">This value can be initialized by App and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
<string name="info_response_card_user_protected_counter">This value can be initialized by App (with PIN2 confirmation) and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>