diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml
index 4548c6069e..09d3bc259e 100644
--- a/.idea/navEditor.xml
+++ b/.idea/navEditor.xml
@@ -8,13 +8,25 @@
@@ -200,8 +274,8 @@
@@ -212,8 +286,8 @@
-
-
+
+
@@ -224,8 +298,8 @@
-
-
+
+
@@ -236,8 +310,8 @@
-
-
+
+
@@ -257,8 +331,8 @@
-
-
+
+
@@ -269,8 +343,8 @@
-
-
+
+
@@ -281,8 +355,8 @@
-
-
+
+
@@ -307,8 +381,8 @@
-
-
+
+
@@ -328,8 +402,8 @@
-
-
+
+
@@ -340,8 +414,8 @@
-
-
+
+
@@ -352,8 +426,8 @@
-
-
+
+
@@ -364,8 +438,8 @@
-
-
+
+
@@ -380,13 +454,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -411,6 +518,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 2f79e7f860..0a834a88d3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -164,4 +164,7 @@ dependencies {
implementation 'io.jafka:jeos:0.9.15'
// implementation 'one.block:eosiojava:0.1.0'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.61'
+
+ implementation 'io.fotoapparat:fotoapparat:2.6.1'
+ implementation 'com.otaliastudios:cameraview:2.6.1'
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java
index e858887213..3f3a033388 100644
--- a/app/src/main/java/com/tangem/data/Blockchain.java
+++ b/app/src/main/java/com/tangem/data/Blockchain.java
@@ -10,6 +10,7 @@ public enum Blockchain {
Bitcoin("BTC", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"),
BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"),
Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
+ EthereumId("ETH/ID", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum ID"),
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"),
Token("Token", "ETH", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
NftToken("NftToken", "", 1.0, R.drawable.tangem2, "Ethereum"),
diff --git a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt
index b470cba5ed..484526bc4d 100644
--- a/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt
+++ b/app/src/main/java/com/tangem/data/network/model/BlockcypherResponse.kt
@@ -35,7 +35,10 @@ data class BlockcypherTxref(
data class BlockcypherTx(
@SerializedName("hex")
- var hex: String? = null
+ var hex: String? = null,
+
+ @SerializedName("addresses")
+ var addesses: List? = null
)
data class BlockcypherFee(
diff --git a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
index ffd3d3f097..a594c110a8 100644
--- a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
@@ -7,7 +7,6 @@ import android.net.Uri
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
-import android.nfc.tech.Ndef
import android.nfc.tech.NfcV
import android.os.Bundle
import android.text.Spannable
@@ -55,7 +54,7 @@ import com.tangem.wallet.BuildConfig
import com.tangem.wallet.CoinEngineFactory
import com.tangem.wallet.R
import com.tangem.wallet.TangemContext
-import com.tangem.wallet.xlmtag.XlmTagEngine
+import com.tangem.wallet.xlmTag.XlmTagEngine
import kotlinx.android.synthetic.main.fragment_main.*
import kotlinx.android.synthetic.main.layout_touch_card.*
import kotlinx.coroutines.CoroutineScope
@@ -98,6 +97,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
super.onViewCreated(view, savedInstanceState)
rippleBackgroundNfc.startRippleAnimation()
+// navigateToDestination(R.id.action_main_to_emptyIdFragment)
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
@@ -274,13 +274,19 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
card.status == TangemCard.Status.Loaded -> lastTag?.let {
val engineCoin = CoinEngineFactory.create(ctx)
if (engineCoin != null) {
- engineCoin.defineWallet()
-
- val bundle = Bundle()
- bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
- ctx.saveToBundle(bundle)
- navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
- R.id.action_main_to_loadedWalletFragment, bundle)
+ if (card.isIDCard) {
+ val bundle = Bundle()
+ bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
+ ctx.saveToBundle(bundle)
+ navigateToDestination(R.id.action_main_to_idFragment, bundle)
+ } else {
+ engineCoin.defineWallet()
+ val bundle = Bundle()
+ bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
+ ctx.saveToBundle(bundle)
+ navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
+ R.id.action_main_to_loadedWalletFragment, bundle)
+ }
} else {
showUnkownBlockchainWarning()
}
diff --git a/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt b/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt
index dd2d27594c..8514ccf37c 100644
--- a/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/additional/TagFragment.kt
@@ -20,7 +20,7 @@ import com.tangem.ui.navigation.NavigationResultListener
import com.tangem.util.LOG
import com.tangem.util.UtilHelper
import com.tangem.wallet.*
-import com.tangem.wallet.xlmtag.XlmTagEngine
+import com.tangem.wallet.xlmTag.XlmTagEngine
import kotlinx.android.synthetic.main.fr_loaded_wallet.*
import kotlinx.android.synthetic.main.layout_btn_details.*
import kotlinx.android.synthetic.main.layout_tangem_card.*
@@ -67,7 +67,7 @@ class TagFragment : BaseFragment(), NavigationResultListener {
btnExtract.backgroundTintList =
ContextCompat.getColorStateList(requireContext(), R.color.btn_dark)
- ivTangemCard.setImageResource(R.drawable.card_default_nft)
+ ivTangemCard.setImageResource(R.drawable.card_tgslix)
tvBalance.setSingleLine(!engine.needMultipleLinesForBalance())
tvWallet.text = ctx.coinData.wallet
@@ -78,7 +78,6 @@ class TagFragment : BaseFragment(), NavigationResultListener {
srl?.setOnRefreshListener { refresh(true) }
requestBalanceAndUnspentTransactions()
-// update()
}
@@ -129,14 +128,13 @@ class TagFragment : BaseFragment(), NavigationResultListener {
else -> tvBalance.text = ""
}
- if (ctx.card!!.tokenSymbol.length > 1) {
- @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
- else
- Html.fromHtml(ctx.blockchainName)
- tvBlockchain.text = html
- } else
- tvBlockchain.text = ctx.blockchainName
+
+ @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
+ else
+ Html.fromHtml(ctx.blockchainName)
+ tvBlockchain.text = html
+
}
diff --git a/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt
new file mode 100644
index 0000000000..24c6143377
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/CameraFragment.kt
@@ -0,0 +1,46 @@
+package com.tangem.ui.fragment.id
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import androidx.core.os.bundleOf
+import com.otaliastudios.cameraview.CameraListener
+import com.otaliastudios.cameraview.PictureResult
+import com.otaliastudios.cameraview.controls.Facing
+import com.tangem.ui.fragment.BaseFragment
+import com.tangem.wallet.R
+import kotlinx.android.synthetic.main.fragment_camera.*
+
+
+class CameraFragment : BaseFragment() {
+ override val layoutId = R.layout.fragment_camera
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ cvCamera?.setLifecycleOwner(viewLifecycleOwner)
+
+ cvCamera?.addCameraListener(object : CameraListener() {
+ override fun onPictureTaken(result: PictureResult) { // Picture was taken!
+
+ result.toBitmap(200, 200) {
+ navigateBackWithResult(Activity.RESULT_OK, bundleOf(PHOTO_KEY to it))
+ }
+ }
+ })
+ ivTakePicture?.setOnClickListener { cvCamera?.takePictureSnapshot() }
+ ivFlipCamera?.setOnClickListener { flipCamera() }
+ }
+
+ private fun flipCamera() {
+ if (cvCamera?.facing == Facing.BACK) {
+ cvCamera?.facing = Facing.FRONT
+ } else {
+ cvCamera?.facing = Facing.BACK
+ }
+ }
+
+ companion object{
+ const val PHOTO_KEY = "photo"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt
new file mode 100644
index 0000000000..76f14bc2bc
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt
@@ -0,0 +1,462 @@
+package com.tangem.ui.fragment.id
+
+import android.app.Activity
+import android.content.SharedPreferences
+import android.graphics.BitmapFactory
+import android.media.MediaPlayer
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import com.tangem.App
+import com.tangem.Constant
+import com.tangem.data.Blockchain
+import com.tangem.data.network.ServerApiCommon
+import com.tangem.server_android.ServerApiTangem
+import com.tangem.server_android.model.CardVerifyAndGetInfo
+import com.tangem.tangem_card.reader.CardProtocol
+import com.tangem.tangem_card.tasks.VerifyCardTask
+import com.tangem.tangem_card.util.Util
+import com.tangem.tangem_sdk.android.reader.NfcReader
+import com.tangem.ui.activity.MainActivity
+import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
+import com.tangem.ui.dialog.WaitSecurityDelayDialog
+import com.tangem.ui.fragment.BaseFragment
+import com.tangem.ui.fragment.wallet.LoadedWalletViewModel
+import com.tangem.util.LOG
+import com.tangem.util.UtilHelper
+import com.tangem.wallet.*
+import kotlinx.android.synthetic.main.fragment_id.*
+import kotlinx.android.synthetic.main.layout_btn_details.*
+import kotlinx.android.synthetic.main.layout_id.*
+import kotlinx.android.synthetic.main.layout_tangem_card.*
+import kotlinx.android.synthetic.main.layout_tangem_card.rlProgressBar
+import net.i2p.crypto.eddsa.Utils
+import java.util.*
+import kotlin.concurrent.timerTask
+
+class IdFragment : BaseFragment(), NfcAdapter.ReaderCallback,
+ CardProtocol.Notifications, SharedPreferences.OnSharedPreferenceChangeListener {
+ companion object {
+ val TAG: String = IdFragment::class.java.simpleName
+ }
+
+ override val layoutId = R.layout.fragment_id
+
+ private lateinit var ctx: TangemContext
+ private lateinit var mpSecondScanSound: MediaPlayer
+ private var serverApiCommon: ServerApiCommon = ServerApiCommon()
+ private var serverApiTangem: ServerApiTangem = ServerApiTangem()
+ private var lastTag: Tag? = null
+ private var lastReadSuccess = true
+ private var verifyCardTask: VerifyCardTask? = null
+ private var requestPIN2Count = 0
+ private var timerHideErrorAndMessage: Timer? = null
+ private var newPIN = ""
+ private var newPIN2 = ""
+ private var photo: ByteArray? = null
+ private var cardProtocol: CardProtocol? = null
+ private var refreshAction: Runnable? = null
+ private var hasIdInfo: Boolean? = null
+ private var toast: Toast? = null
+
+ private var requestCounter: Int = 0
+ set(value) {
+ field = value
+ LOG.i(TAG, "requestCounter, set $field")
+ if (field <= 0) {
+ LOG.e(TAG, "+++++++++++ FINISH REFRESH")
+ if (srl != null && srl.isRefreshing)
+ srl.isRefreshing = false
+ } else if (srl != null && !srl.isRefreshing)
+ srl.isRefreshing = true
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ctx = TangemContext.loadFromBundle(context, arguments)
+
+ lastTag = activity?.intent?.getParcelableExtra(Constant.EXTRA_LAST_DISCOVERED_TAG)
+
+ if (arguments?.containsKey(NfcAdapter.EXTRA_TAG) == true) {
+ val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG)
+ if (tag != null) onTagDiscovered(tag)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ mpSecondScanSound = MediaPlayer.create(activity, R.raw.scan_card_sound)
+
+ tvIdNumber?.text = "ID # ${Utils.bytesToHex(ctx.card.cid)}"
+
+ if (hasIdInfo == false) rlToolButtons?.visibility = View.VISIBLE
+
+ // set listeners
+ srl.setOnRefreshListener { refresh() }
+
+ btnDetails.setOnClickListener {
+ if (cardProtocol != null) {
+ val bundle = Bundle().apply { ctx.saveToBundle(this) }
+ navigateForResult(Constant.REQUEST_CODE_VERIFY_CARD,
+ R.id.action_idFragment_to_verifyCard, bundle)
+ } else {
+ (activity as MainActivity).toastHelper
+ .showSingleToast(context, getString(R.string.general_notification_scan_again_to_verify))
+ }
+ }
+
+ btnIssueNewId?.setOnClickListener {
+ val bundle = Bundle().apply { ctx.saveToBundle(this) }
+ navigateToDestination(R.id.action_idFragment_to_issueNewIdFragment, bundle)
+ }
+
+ if (hasIdInfo == null) tvBalanceLine1?.text = "Reading... Hold the card firmly"
+
+ btnNewScan.setOnClickListener {
+ navigateUp()
+ }
+
+ // request card verify and get info listener
+ val cardVerifyAndGetInfoListener: ServerApiTangem.CardVerifyAndGetInfoListener = object : ServerApiTangem.CardVerifyAndGetInfoListener {
+ override fun onSuccess(cardVerifyAndGetArtworkResponse: CardVerifyAndGetInfo.Response?) {
+ LOG.i(TAG, "cardVerifyAndGetInfoListener onSuccess")
+ if (activity == null || !UtilHelper.isOnline(activity!!)) return
+
+ val result = cardVerifyAndGetArtworkResponse?.results!![0]
+ if (result.error != null) {
+ ctx.card!!.isOnlineVerified = false
+ return
+ }
+ ctx.card!!.isOnlineVerified = result.passed
+
+ requestCounter--
+ updateViews()
+
+ if (!result.passed) return
+
+ if (App.localStorage.checkBatchInfoChanged(ctx.card!!, result)) {
+ LOG.w(TAG, "Batch ${result.batch} info changed to '$result'")
+ ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!))
+ App.localStorage.applySubstitution(ctx.card!!)
+ refresh()
+ }
+ }
+
+ override fun onFail(message: String?) {
+ LOG.i(TAG, "cardVerifyAndGetInfoListener onFail")
+ if (activity == null || !UtilHelper.isOnline(activity!!)) return
+ requestCounter--
+ updateViews()
+ }
+ }
+ serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener)
+ refresh()
+ startVerify(lastTag)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (timerHideErrorAndMessage != null) {
+ timerHideErrorAndMessage!!.cancel()
+ timerHideErrorAndMessage = null
+ }
+ }
+
+ override fun onStop() {
+ srl?.removeCallbacks(refreshAction)
+ toast?.cancel()
+ super.onStop()
+ }
+
+
+ override fun onTagDiscovered(tag: Tag) {
+ startVerify(tag)
+ }
+
+ override fun onReadStart(cardProtocol: CardProtocol) {
+ rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE }
+ }
+
+ override fun onReadProgress(protocol: CardProtocol, progress: Int) {
+
+ }
+
+ override fun onReadFinish(cardProtocol: CardProtocol?) {
+ verifyCardTask = null
+ if (cardProtocol != null) {
+ if (cardProtocol.error == null) {
+ rlProgressBar?.post {
+ rlProgressBar?.visibility = View.GONE
+ this.cardProtocol = cardProtocol
+ if (!cardProtocol.card.isWalletPublicKeyValid)
+ refresh()
+ else
+ updateViews()
+
+ if (cardProtocol.card.isIDCard) {
+ if (cardProtocol.card.hasIDCardData()) {
+
+ ctx.card = cardProtocol.card
+
+ tvBalanceLine1?.visibility = View.VISIBLE
+
+ val idCardData = ctx.card.idCardData
+ tvFullName.text = idCardData.fullName
+ tvBirthDate.text = idCardData.birthday
+ tvGender.text = "Sex: ${idCardData.gender}"
+ ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(idCardData.photo, 0, idCardData.photo.size))
+
+ val inputs = "${idCardData.fullName};${idCardData.birthday}${idCardData.gender}"
+ val info = inputs.toByteArray() + idCardData.photo
+ ctx.card.idHash = Util.calculateSHA256(info)
+
+ refresh()
+
+ } else {
+ rlToolButtons?.visibility = View.VISIBLE
+ tvBalanceLine1?.text = resources.getString(R.string.id_empty).toUpperCase(Locale.ENGLISH)
+ tvBalanceLine1?.setTextSize(18F)
+ tvBalanceLine1?.visibility = View.VISIBLE
+ hasIdInfo = false
+ }
+ }
+
+ mpSecondScanSound.start()
+ }
+ } else {
+ // remove last UIDs because of error and no card read
+ rlProgressBar?.post {
+ lastReadSuccess = false
+ if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported)
+ if (!NoExtendedLengthSupportDialog.allReadyShowed)
+ activity?.supportFragmentManager?.let { NoExtendedLengthSupportDialog().show(it, NoExtendedLengthSupportDialog.TAG) }
+ else {
+ toast = Toast.makeText(activity, R.string.general_notification_scan_again, Toast.LENGTH_SHORT)
+ toast?.show()
+ }
+ }
+ }
+ }
+
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+ override fun onReadCancel() {
+ verifyCardTask = null
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+ override fun onReadWait(msec: Int) {
+ WaitSecurityDelayDialog.onReadWait(activity as AppCompatActivity?, msec)
+ }
+
+ override fun onReadBeforeRequest(timeout: Int) {
+ WaitSecurityDelayDialog.onReadBeforeRequest(activity as AppCompatActivity?, timeout)
+ }
+
+ override fun onReadAfterRequest() {
+ WaitSecurityDelayDialog.onReadAfterRequest(activity)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+
+ }
+
+ fun updateViews() {
+ if (activity == null || this.view == null) return
+
+ if (timerHideErrorAndMessage != null) {
+ timerHideErrorAndMessage!!.cancel()
+ timerHideErrorAndMessage = null
+ }
+
+ if (ctx.card?.idCardData?.photo != null && (photo == null)) {
+ photo = ctx.card.idCardData.photo
+ ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(photo, 0, photo!!.size))
+
+ }
+
+ if (ctx.hasError()) {
+ tvError?.visibility = View.VISIBLE
+ tvError?.text = ctx.error
+ } else {
+ tvError?.visibility = View.GONE
+ tvError?.text = ""
+ }
+
+ if (ctx.message == null || ctx.message.isEmpty()) {
+ tvMessage?.text = ""
+ tvMessage?.visibility = View.GONE
+ } else {
+ tvMessage?.text = ctx.message
+ tvMessage?.visibility = View.VISIBLE
+ }
+
+ if (tvError?.visibility == View.VISIBLE || tvMessage?.visibility == View.VISIBLE) {
+ timerHideErrorAndMessage = Timer()
+ timerHideErrorAndMessage?.schedule(
+ timerTask {
+ activity?.runOnUiThread {
+ tvMessage?.visibility = View.GONE
+ tvError?.visibility = View.GONE
+ // clear only already viewed messages
+ if (tvMessage?.text == ctx.message) ctx.message = null
+ if (tvError?.text == ctx.error) ctx.error = null
+ }
+ },
+ 5000)
+ }
+
+ if (srl.isRefreshing && ctx.card?.hasIDCardData() != false) {
+ tvBalanceLine1.setTextColor(resources.getColor(R.color.primary))
+ tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain)
+ tvBalanceLine2.text = ""
+ } else if (ctx.card?.hasIDCardData() != false) {
+
+ val validator = BalanceValidator()
+ // TODO why attest=false?
+ validator.check(ctx, false)
+ context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) }
+ tvBalanceLine1?.text = getString(validator.firstLine)
+ }
+
+ if (ctx.card.hasIDCardData()) {
+ val idCardData = ctx.card.idCardData
+ tvFullName.text = idCardData.fullName
+ tvBirthDate.text = idCardData.birthday
+ tvGender.text = "Sex: ${idCardData.gender}"
+ ivPhoto.setImageBitmap(BitmapFactory.decodeByteArray(idCardData.photo, 0, idCardData.photo.size))
+ rlToolButtons?.visibility = View.GONE
+ }
+ }
+
+ private fun refresh() {
+ if (ctx.card == null) return
+
+ // clear all card data and request again
+ ctx.coinData.clearInfo()
+ ctx.error = null
+ ctx.message = null
+
+ LOG.w(TAG, "============= START REFRESH")
+ requestCounter = 0
+ srl?.isRefreshing = true
+
+ updateViews()
+
+ // Bitcoin, Litecoin, BitcoinCash, Stellar
+ if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet ||
+ ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash ||
+ ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) {
+ ctx.coinData.setIsBalanceEqual(true)
+ }
+
+ requestVerifyAndGetInfo()
+
+ if (ctx.card.hasIDCardData()) requestBalanceAndUnspentTransactions()
+
+ if (requestCounter == 0) {
+ // if no connection and no requests posted
+ srl?.isRefreshing = false
+ updateViews()
+ }
+ }
+
+ private fun requestBalanceAndUnspentTransactions() {
+ if (UtilHelper.isOnline(context as Activity)) {
+ val coinEngine = CoinEngineFactory.create(ctx)
+ coinEngine?.defineWallet()
+ requestCounter++
+ coinEngine!!.requestBalanceAndUnspentTransactions(
+ object : CoinEngine.BlockchainRequestsCallbacks {
+
+
+ override fun onComplete(success: Boolean) {
+ LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter")
+ if (activity == null) return
+ requestCounter--
+ if (!success) {
+ LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error)
+ }
+ updateViews()
+
+ }
+
+ override fun onProgress() {
+ if (activity == null) return
+ LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress")
+ updateViews()
+ }
+
+ override fun allowAdvance(): Boolean {
+ return try {
+ context?.let { UtilHelper.isOnline(it) }!!
+ } catch (e: KotlinNullPointerException) {
+ e.printStackTrace()
+ false
+ }
+ }
+ }
+ )
+ } else {
+ ctx.error = getString(R.string.general_error_no_connection)
+ updateViews()
+ }
+ }
+
+ private fun requestVerifyAndGetInfo() {
+ if (UtilHelper.isOnline(context as Activity)) {
+ if ((ctx.card!!.isOnlineVerified == null || !ctx.card!!.isOnlineVerified)) {
+ LOG.i(TAG, "requestVerifyAndGetInfo")
+ requestCounter++
+ serverApiTangem.cardVerifyAndGetInfo(ctx.card)
+ }
+ } else {
+ ctx.error = getString(R.string.general_error_no_connection)
+ updateViews()
+ }
+ }
+
+ private fun startVerify(tag: Tag?) {
+ try {
+ val isoDep = IsoDep.get(tag)
+ val uid = tag?.id
+ val sUID = Util.byteArrayToHexString(uid)
+ if (ctx.card.uid != sUID || cardProtocol != null) {
+ (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag)
+ return
+ }
+
+ if (lastReadSuccess)
+ isoDep.timeout = 1000
+ else
+ isoDep.timeout = 65000
+
+ verifyCardTask = VerifyCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep),
+ App.localStorage, App.pinStorage, App.firmwaresStorage, this)
+ verifyCardTask?.start()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt
new file mode 100644
index 0000000000..d61191457f
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/IssueNewIdFragment.kt
@@ -0,0 +1,253 @@
+package com.tangem.ui.fragment.id
+
+import android.app.Activity
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.os.Parcelable
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.widget.EditText
+import android.widget.RadioButton
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.core.os.bundleOf
+import com.tangem.tangem_card.util.Util
+import com.tangem.ui.fragment.BaseFragment
+import com.tangem.ui.navigation.NavigationResultListener
+import com.tangem.wallet.CoinEngineFactory
+import com.tangem.wallet.R
+import com.tangem.wallet.TangemContext
+import com.tangem.wallet.ethID.EthIdEngine
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.fragment_issue_new_id.*
+import java.io.ByteArrayOutputStream
+import java.lang.Exception
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+class IssueNewIdFragment : BaseFragment(), NavigationResultListener {
+ lateinit var ctx: TangemContext
+ private var photo: Bitmap? = null
+ private var photoInBytes: ByteArray? = null
+ private var gender: Gender? = null
+ private var userIdData: UserIdData? = null
+
+ override val layoutId = R.layout.fragment_issue_new_id
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ ctx = TangemContext.loadFromBundle(context, arguments)
+
+ ivPhoto?.setOnClickListener {
+ navigateForResult(GET_PHOTO_REQUEST_CODE, R.id.action_issueNewIdFragment_to_cameraFragment)
+ }
+
+ if (photo != null) ivPhoto?.setImageBitmap(photo)
+
+ btnConfirm?.setOnClickListener {
+ if (!checkIfInfoProvided()) {
+ Toast.makeText(context, "Fill in all data first", Toast.LENGTH_LONG).show()
+ return@setOnClickListener
+ }
+ try {
+ processData()
+ } catch (exception: Exception) {
+ Toast.makeText(context, "Check for correctness of data", Toast.LENGTH_LONG).show()
+ return@setOnClickListener
+ }
+ val bundle = bundleOf(USER_ID_DATA to userIdData)
+ ctx.saveToBundle(bundle)
+ navigateToDestination(R.id.action_issueNewIdFragment_to_validateIdFragment, bundle)
+ }
+
+ rbFemale?.setOnClickListener { onRadioButtonClicked(it) }
+ rbMale?.setOnClickListener { onRadioButtonClicked(it) }
+
+ DateInputMask(etBirthDate).listen()
+// etBirthDate?.addTextChangedListener(MaskWatcher("##/##/####"))
+
+ }
+
+ private fun onRadioButtonClicked(view: View) {
+ if (view is RadioButton) {
+ when (view.getId()) {
+ R.id.rbFemale ->
+ if (view.isChecked) {
+ gender = Gender.F
+ }
+ R.id.rbMale ->
+ if (view.isChecked) {
+ gender = Gender.M
+ }
+ }
+ }
+ }
+
+ override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
+ if (requestCode == GET_PHOTO_REQUEST_CODE) {
+ if (resultCode == Activity.RESULT_OK) {
+ photo = data?.getParcelable(CameraFragment.PHOTO_KEY)
+ ivPhoto?.setImageBitmap(photo)
+ val out = ByteArrayOutputStream()
+ photo!!.compress(Bitmap.CompressFormat.JPEG, 100, out)
+ photoInBytes = out.toByteArray()
+ }
+ }
+ }
+
+
+ private fun checkIfInfoProvided(): Boolean {
+ return (etName.text.isNotBlank() && etSurname.text.isNotBlank() && etBirthDate.text.isNotBlank() &&
+ (photo != null) && (gender != null))
+ }
+
+ private fun processData() {
+ val name = etName.text.toString()
+ val lastName = etSurname.text.toString()
+ val birthDate = convertDate(etBirthDate.text)
+ userIdData = UserIdData(name, lastName, birthDate, gender.toString(), photo!!)
+ val issuerExpireDate = processIssueExpireDate()
+
+ ctx.card.setTlvIDCardData(
+ "$name $lastName",
+ birthDate,
+ gender.toString(),
+ photoInBytes,
+ issuerExpireDate.issueDate,
+ issuerExpireDate.expireDate,
+ (CoinEngineFactory.create(ctx) as? EthIdEngine)?.approvalAddress
+ )
+
+ val inputs = "$name $lastName;$birthDate${gender.toString()}"
+ val info = inputs.toByteArray() + photoInBytes!!
+ ctx.card.idHash = Util.calculateSHA256(info)
+ }
+
+ private fun convertDate(inputDate: CharSequence): String {
+ val calendar = Calendar.getInstance()
+ calendar.clear()
+ val sdf = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH)
+ try {
+ calendar.time = sdf.parse(inputDate.toString())
+ } catch (exception: Error) {
+ etBirthDate.setTextColor(ContextCompat.getColor(requireContext(), R.color.bg_err))
+ }
+ calendar.set(Calendar.HOUR_OF_DAY, 0)
+ calendar.timeZone = SimpleTimeZone(0, "UTC")
+ val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH)
+ dateFormat.timeZone = SimpleTimeZone(0, "UTC")
+ return dateFormat.format(calendar.time)
+ }
+
+ private fun processIssueExpireDate(): IssuerExpireDate {
+ val dt = Date()
+ val issueDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt)
+ dt.year = dt.year + 10
+ val expireDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt)
+ return IssuerExpireDate(issueDate, expireDate)
+ }
+
+ data class IssuerExpireDate(val issueDate: String, val expireDate: String)
+
+ companion object {
+ const val SEPARATOR = ";"
+ const val COMPUTED_PUB_KEY = "ComputedPubKey"
+ const val GET_PHOTO_REQUEST_CODE = "GetPhoto"
+ const val USER_ID_DATA = "UserIdData"
+ }
+}
+
+
+enum class Gender { M, F }
+
+@Parcelize
+data class UserIdData(
+ val firstName: String,
+ val lastName: String,
+ val birthDate: String,
+ val gender: String,
+ val photo: Bitmap
+) : Parcelable
+
+class DateInputMask(val input: EditText) {
+ fun listen() {
+ input.addTextChangedListener(dateEntryWatcher)
+ }
+
+ private val dateEntryWatcher = object : TextWatcher {
+
+ var edited = false
+ val dividerCharacter = "/"
+
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ if (edited) {
+ edited = false
+ return
+ }
+
+ var working = getEditText()
+
+ working = manageDateDivider(working, 2, start, before)
+ working = manageDateDivider(working, 5, start, before)
+
+ edited = true
+ input.setText(working)
+ input.setSelection(input.text.length)
+ }
+
+ private fun manageDateDivider(working: String, position: Int, start: Int, before: Int): String {
+ if (working.length == position) {
+ return if (before <= position && start < position)
+ working + dividerCharacter
+ else
+ working.dropLast(1)
+ }
+ return working
+ }
+
+ private fun getEditText(): String {
+ return if (input.text.length >= 10)
+ input.text.toString().substring(0, 10)
+ else
+ input.text.toString()
+ }
+
+ override fun afterTextChanged(s: Editable) {}
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ }
+}
+
+class MaskWatcher(private val mask: String) : TextWatcher {
+ private var isRunning = false
+ private var isDeleting = false
+ override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {
+ isDeleting = count > after
+ }
+
+ override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {}
+ override fun afterTextChanged(editable: Editable) {
+ if (isRunning || isDeleting) {
+ return
+ }
+ isRunning = true
+ val editableLength = editable.length
+ if (editableLength < mask.length) {
+ if (mask[editableLength] != '#') {
+ editable.append(mask[editableLength])
+ } else if (mask[editableLength - 1] != '#') {
+ editable.insert(editableLength - 1, mask, editableLength - 1, editableLength)
+ }
+ }
+ if (editableLength > mask.length) editable.delete(editable.lastIndex, editableLength)
+ isRunning = false
+ }
+
+ companion object {
+ fun buildCpf(): MaskWatcher {
+ return MaskWatcher("###.###.###-##")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt
new file mode 100644
index 0000000000..04812351c0
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt
@@ -0,0 +1,323 @@
+package com.tangem.ui.fragment.id
+
+import android.app.Activity
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.media.MediaPlayer
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import com.tangem.App
+import com.tangem.Constant
+import com.tangem.tangem_card.data.TangemCard
+import com.tangem.tangem_card.reader.CardProtocol
+import com.tangem.tangem_card.tasks.OneTouchSignTask
+import com.tangem.tangem_card.tasks.SignTask
+import com.tangem.tangem_card.util.Util
+import com.tangem.tangem_sdk.android.data.PINStorage
+import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
+import com.tangem.tangem_sdk.android.reader.NfcReader
+import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
+import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
+import com.tangem.tangem_sdk.data.asBundle
+import com.tangem.ui.SignTransactionFragment
+import com.tangem.ui.activity.MainActivity
+import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
+import com.tangem.ui.dialog.WaitSecurityDelayDialog
+import com.tangem.ui.fragment.BaseFragment
+import com.tangem.ui.navigation.NavigationResultListener
+import com.tangem.util.LOG
+import com.tangem.wallet.CoinEngine
+import com.tangem.wallet.CoinEngineFactory
+import com.tangem.wallet.R
+import com.tangem.wallet.TangemContext
+import kotlinx.android.synthetic.main.layout_progress_horizontal.*
+import kotlinx.android.synthetic.main.layout_touch_card.*
+
+class ValidateIdFragment : BaseFragment(), NavigationResultListener,
+ NfcAdapter.ReaderCallback, CardProtocol.Notifications {
+
+ companion object {
+ val TAG: String = ValidateIdFragment::class.java.simpleName
+ }
+
+ override val layoutId = R.layout.fragment_validate_id
+
+ private lateinit var ctx: TangemContext
+ private lateinit var mpFinishSignSound: MediaPlayer
+
+ private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
+
+ private var signTransactionTask: OneTouchSignTask? = null
+
+ private lateinit var amount: CoinEngine.Amount
+ private lateinit var fee: CoinEngine.Amount
+ private var isIncludeFee = true
+ private var outAddressStr: String? = null
+ private var lastReadSuccess = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ctx = TangemContext.loadFromBundle(context, arguments)
+
+ val callback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ navigateBackWithResult(Activity.RESULT_CANCELED)
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, callback)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
+
+ // init NFC Antenna
+ nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
+ nfcDeviceAntenna.init()
+
+ progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar.visibility = View.INVISIBLE
+ }
+
+ override fun onPause() {
+ signTransactionTask?.cancel(true)
+ super.onPause()
+ }
+
+ override fun onStop() {
+ signTransactionTask?.cancel(true)
+ super.onStop()
+ }
+
+ override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
+ if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
+ navigateBackWithResult(resultCode, data)
+ }
+ }
+
+ override fun onTagDiscovered(tag: Tag) {
+ try {
+ // get IsoDep handle and run cardReader thread
+ val isoDep = IsoDep.get(tag)
+ val uid = tag.id
+ val sUID = Util.byteArrayToHexString(uid)
+
+ if (lastReadSuccess)
+ isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000
+ else
+ isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000
+
+ val coinEngine = CoinEngineFactory.create(ctx)
+ coinEngine?.defineWallet()
+
+ coinEngine?.setOnNeedSendTransaction { tx ->
+ if (tx != null) {
+ val data = Bundle()
+ ctx.saveToBundle(data)
+ data.putByteArray(Constant.EXTRA_TX, tx)
+ navigateForResult(
+ Constant.REQUEST_CODE_SEND_TRANSACTION_,
+ R.id.action_validateIdFragment_to_writeIdFragment,
+ data)
+ }
+ }
+
+ coinEngine?.requestBalanceAndUnspentTransactions(object : CoinEngine.BlockchainRequestsCallbacks {
+ override fun onComplete(success: Boolean?) {
+ val transactionToSign = coinEngine.constructTransaction(null, null, true, null)
+
+ val transaction = object : OneTouchSignTask.TransactionToSign{
+ override fun isIssuerCanSignTransaction(card: TangemCard?): Boolean {
+ return false
+ }
+
+ override fun isIssuerCanSignData(card: TangemCard?): Boolean {
+ return false
+ }
+
+ override fun getHashAlgToSign(card: TangemCard?): String {
+ return transactionToSign.hashAlgToSign
+ }
+
+ override fun getRawDataToSign(card: TangemCard?): ByteArray {
+ return transactionToSign.rawDataToSign
+ }
+
+ override fun isSigningOnCardSupported(card: TangemCard?): Boolean {
+ return true
+ }
+
+ override fun onSignCompleted(card: TangemCard?, signature: ByteArray?) {
+ transactionToSign.onSignCompleted(signature)
+ }
+
+ override fun getHashesToSign(card: TangemCard?): Array {
+ return transactionToSign.hashesToSign
+ }
+
+ override fun getIssuerTransactionSignature(card: TangemCard?, dataToSignByIssuer: ByteArray?): ByteArray {
+ return transactionToSign.getIssuerTransactionSignature(dataToSignByIssuer)
+ }
+
+ }
+
+ PINStorage.setPIN2(PINStorage.getDefaultPIN2())
+ signTransactionTask = OneTouchSignTask(NfcReader((activity as MainActivity).nfcManager, isoDep),
+ App.localStorage, App.pinStorage, this@ValidateIdFragment, transaction)
+ signTransactionTask?.start()
+ }
+
+ override fun onProgress() {
+ }
+
+ override fun allowAdvance(): Boolean {
+ return true
+ }
+ })
+
+
+ } catch (e: CardProtocol.TangemException_WrongAmount) {
+ try {
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
+ data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle)
+ navigateBackWithResult(Activity.RESULT_CANCELED, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ override fun onReadStart(cardProtocol: CardProtocol) {
+ rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
+
+ progressBar?.post {
+ progressBar?.visibility = View.VISIBLE
+ progressBar?.progress = 5
+ }
+ }
+
+ override fun onReadProgress(protocol: CardProtocol, progress: Int) {
+ progressBar?.post { progressBar?.progress = progress }
+ }
+
+ override fun onReadFinish(cardProtocol: CardProtocol?) {
+ signTransactionTask = null
+ if (cardProtocol != null) {
+ if (cardProtocol.error == null) {
+ rlProgressBar?.post { rlProgressBar?.visibility = View.GONE }
+
+ progressBar?.post {
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN)
+ }
+
+ mpFinishSignSound.start()
+ } else {
+ lastReadSuccess = false
+ if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) {
+ progressBar?.post {
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
+ }
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign))
+ data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
+ navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ } else {
+ if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
+ try {
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
+ data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
+ navigateBackWithResult(Activity.RESULT_CANCELED, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ progressBar?.post {
+ if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
+ if (!NoExtendedLengthSupportDialog.allReadyShowed) {
+ NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString()
+ NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
+ }
+ } else {
+ Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_LONG).show()
+ }
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
+ }
+ }
+ }
+ }
+
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+ override fun onReadCancel() {
+ signTransactionTask = null
+
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+
+ override fun onReadBeforeRequest(timeout: Int) {
+ LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
+ WaitSecurityDelayDialog.onReadBeforeRequest(activity!!, timeout)
+ }
+
+ override fun onReadAfterRequest() {
+ LOG.i(TAG, "onReadAfterRequest")
+ WaitSecurityDelayDialog.onReadAfterRequest(activity!!)
+ }
+
+ override fun onReadWait(msec: Int) {
+ LOG.i(TAG, "onReadWait msec $msec")
+ WaitSecurityDelayDialog.onReadWait(activity!!, msec)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt
new file mode 100644
index 0000000000..5b0235f356
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt
@@ -0,0 +1,292 @@
+package com.tangem.ui.fragment.id
+
+import android.app.Activity
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.media.MediaPlayer
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import com.tangem.App
+import com.tangem.Constant
+import com.tangem.tangem_card.reader.CardProtocol
+import com.tangem.tangem_card.tasks.WriteIDCardTask
+import com.tangem.tangem_card.util.Util
+import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
+import com.tangem.tangem_sdk.android.reader.NfcReader
+import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
+import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
+import com.tangem.tangem_sdk.data.asBundle
+import com.tangem.ui.activity.MainActivity
+import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
+import com.tangem.ui.dialog.WaitSecurityDelayDialog
+import com.tangem.ui.fragment.BaseFragment
+import com.tangem.ui.navigation.NavigationResultListener
+import com.tangem.util.LOG
+import com.tangem.util.UtilHelper
+import com.tangem.wallet.CoinEngine
+import com.tangem.wallet.CoinEngineFactory
+import com.tangem.wallet.R
+import com.tangem.wallet.TangemContext
+import kotlinx.android.synthetic.main.layout_progress_horizontal.*
+import kotlinx.android.synthetic.main.layout_touch_card.*
+import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.*
+
+class WriteIdFragment : BaseFragment(), NavigationResultListener,
+ NfcAdapter.ReaderCallback, CardProtocol.Notifications {
+
+ companion object {
+ val TAG: String = WriteIdFragment::class.java.simpleName
+ }
+
+ override val layoutId = R.layout.fragment_write_id
+
+ private lateinit var ctx: TangemContext
+ private lateinit var tx: ByteArray
+ private lateinit var mpFinishSignSound: MediaPlayer
+ private var toast: Toast? = null
+
+ private var idWasWritten = false
+
+ private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
+
+ private var writeIDCardTask: WriteIDCardTask? = null
+
+ private var lastReadSuccess = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ctx = TangemContext.loadFromBundle(context, arguments)
+ tx = arguments?.getByteArray(Constant.EXTRA_TX)!!
+
+ val callback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ navigateBackWithResult(Activity.RESULT_CANCELED)
+ }
+ }
+ requireActivity().onBackPressedDispatcher.addCallback(this, callback)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
+
+ // init NFC Antenna
+ nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
+ nfcDeviceAntenna.init()
+
+ tvCardID.text = ctx.card!!.cidDescription
+ progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar.visibility = View.INVISIBLE
+ tvProgressBar?.text = "Writing..."
+ }
+
+ override fun onPause() {
+ writeIDCardTask?.cancel(true)
+ super.onPause()
+ }
+
+ override fun onStop() {
+ writeIDCardTask?.cancel(true)
+ toast?.cancel()
+ super.onStop()
+ }
+
+ override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
+ if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
+ navigateBackWithResult(resultCode, data)
+ }
+ }
+
+ override fun onTagDiscovered(tag: Tag) {
+ try {
+ // get IsoDep handle and run cardReader thread
+ val isoDep = IsoDep.get(tag)
+ val uid = tag.id
+ val sUID = Util.byteArrayToHexString(uid)
+
+ if (sUID == ctx.card.uid || !idWasWritten) {
+ if (lastReadSuccess)
+ isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000
+ else
+ isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000
+
+ writeIDCardTask = WriteIDCardTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep),
+ App.localStorage, App.pinStorage, ctx.card.idCardData, ctx.card.issuer.privateDataKey, this)
+ writeIDCardTask?.start()
+ } else
+ (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag)
+
+ } catch (e: CardProtocol.TangemException_WrongAmount) {
+ try {
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
+ data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle)
+ navigateBackWithResult(Activity.RESULT_CANCELED, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ override fun onReadStart(cardProtocol: CardProtocol) {
+ rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
+
+ progressBar?.post {
+ progressBar?.visibility = View.VISIBLE
+ progressBar?.progress = 5
+ }
+ }
+
+ override fun onReadProgress(protocol: CardProtocol, progress: Int) {
+ progressBar?.post { progressBar?.progress = progress }
+ }
+
+ override fun onReadFinish(cardProtocol: CardProtocol?) {
+ writeIDCardTask = null
+ if (cardProtocol != null) {
+ if (cardProtocol.error == null) {
+ rlProgressBar?.post { rlProgressBar?.visibility = View.GONE }
+
+ progressBar?.post {
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN)
+ }
+
+ mpFinishSignSound.start()
+
+ idWasWritten = true
+
+ val coinEngine = CoinEngineFactory.create(ctx)
+
+ coinEngine!!.requestSendTransaction(
+ object : CoinEngine.BlockchainRequestsCallbacks {
+ override fun onComplete(success: Boolean) {
+ if (success) {
+ navigateUp(R.id.main)
+ } else {
+ toast?.cancel()
+ navigateUp(R.id.issueNewIdFragment)
+ }
+ }
+
+ override fun onProgress() {
+ }
+
+ override fun allowAdvance(): Boolean {
+ return UtilHelper.isOnline(requireContext())
+ }
+ },
+ tx
+ )
+
+ } else {
+ lastReadSuccess = false
+ if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) {
+ progressBar?.post {
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
+ }
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign))
+ data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
+ navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ } else {
+ if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
+ try {
+ val data = Bundle()
+ data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
+ data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
+ data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
+ navigateBackWithResult(Activity.RESULT_CANCELED, data)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ progressBar?.post {
+ if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
+ if (!NoExtendedLengthSupportDialog.allReadyShowed) {
+ NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString()
+ NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
+ }
+ } else {
+ if (!idWasWritten) {
+ toast = Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_SHORT)
+ toast?.show()
+ }
+ }
+ progressBar?.progress = 100
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
+ }
+ }
+ }
+ }
+
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+ override fun onReadCancel() {
+ writeIDCardTask = null
+
+ progressBar?.postDelayed({
+ try {
+ progressBar?.progress = 0
+ progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
+ progressBar?.visibility = View.INVISIBLE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+ }
+
+
+ override fun onReadBeforeRequest(timeout: Int) {
+ LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
+ WaitSecurityDelayDialog.onReadBeforeRequest(activity!!, timeout)
+ }
+
+ override fun onReadAfterRequest() {
+ LOG.i(TAG, "onReadAfterRequest")
+ WaitSecurityDelayDialog.onReadAfterRequest(activity!!)
+ }
+
+ override fun onReadWait(msec: Int) {
+ LOG.i(TAG, "onReadWait msec $msec")
+ WaitSecurityDelayDialog.onReadWait(activity!!, msec)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt
index 31ca17f0dd..db7e155e92 100644
--- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt
@@ -30,6 +30,7 @@ import com.tangem.server_android.model.CardVerifyAndGetInfo
import com.tangem.tangem_card.data.TangemCard
import com.tangem.tangem_card.reader.CardProtocol
import com.tangem.tangem_card.tasks.VerifyCardTask
+import com.tangem.tangem_card.tasks.WriteIDCardTask
import com.tangem.tangem_card.util.Util
import com.tangem.tangem_sdk.android.reader.NfcReader
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
@@ -55,6 +56,8 @@ import kotlinx.android.synthetic.main.layout_tangem_card.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.timerTask
@@ -525,6 +528,19 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte
else
updateViews()
+ // TODO - remove, only for test&demo reason!
+ // TODO - begin
+ if( cardProtocol.card.isIDCard)
+ {
+ if( cardProtocol.card.hasIDCardData() )
+ {
+ Toast.makeText(activity, "ID card: ${cardProtocol.card.idCardData.fullName}", Toast.LENGTH_SHORT).show()
+ }else{
+ Toast.makeText(activity, "Empty ID card", Toast.LENGTH_SHORT).show()
+ }
+ }
+ // TODO - end
+
mpSecondScanSound.start()
}
} else {
@@ -789,6 +805,65 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte
val uid = tag?.id
val sUID = Util.byteArrayToHexString(uid)
if (ctx.card.uid != sUID || cardProtocol != null) {
+
+ //TODO - remove, only for test
+ //TODO - begin
+ if( ctx.card.isIDCard && !ctx.card.hasIDCardData() )
+ {
+ val idCardData=TangemCard.IDCardData()
+ idCardData.fullName="John Doe"
+ idCardData.birthday="1980-1-1"
+ idCardData.gender="M"
+ val dt= Date()
+ idCardData.issueDate= SimpleDateFormat ("yyyy-MM-dd", Locale.US).format(dt)
+ dt.year = dt.year + 10
+ idCardData.expireDate=SimpleDateFormat("yyyy-MM-dd", Locale.US).format(dt)
+ idCardData.trustedAddress="GCOQLQT32RVH2MTWQGK5K3GIGT3STYG5323KMNTTFHIU2T5TFOGR4KDT" // ask D.Baturin
+ idCardData.photo=Util.hexToBytes("FFD8FFE000104A46494600010101012C012C0000FFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D15161118231F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3BFFC000110800C8007903012200021101031101FFC4001C0000020203010100000000000000000000000706080103050402FFC4003F100001030301050505060406020300000001020304000511060712213141135161718114223291A115235262B1C1428292D10816333472A2175393D2E1FFC4001801000301010000000000000000000000000002030104FFC4002111000202030100020301000000000000000001021103213112132232415142FFDA000C03010002110311003F007351451400562B35A83ED29F53097505D424294807DE4839C123B8E0FCA8016BB4CD712AC97FB642B72FDF8AA12A4273C179C84A0F811BC7D45306CF768B7BB547B9425EFB0FA3793DE3BC1F10720D579D75297335C5E1D5924A64A9B19EE4FBA3F4AECECDB5B7F96AE2604E708B64B57BC4F2617C82FC8F23E87A54164FB6C771D0FBA2BE50B4AD0149214923208E44566AE219A28A2800A28A2800A28A2800A28A2800A28A2803E5440049E02ABEFF009F26C5DA0C9D44CA94B69C74B6A673C16C03809F900478D580791DAB2B6F38DF494E7CEAA9BAD165E5B4AF89B5149F307151CADAAA1A28ECEB35B0FEAB9D322381C8D31424B4B1D52B483F30720F8835C4A3248009381CBC28AE76ED9444C7496D2AEDA65B44375227C04F04B2E2B0A6C7E45741E07879533AD7B55D2B7040ED662A0B879A2520A47F50C8FAD2028A78E468571459E6F53D85E46FB77AB7A93DE24A3FBD706EBB54D2D6DCA5B98A9CE0FE18A8DE1FD4709FAD57EC03D07CAB34CF3333C967B4E5F19D47638D7561B53687C1FBB5104A48241071E55EE932E3C3614FCA7DB61A4FC4E38A0948F5355F6C1B43BBE9BB02ED36F6D8CA9E538979D05451903202797319E3DFCAB8574BD5CEF6FF006D739CF4B5E78768AC84F90E43D053FCAA8CF2588B36AFB46A0B9C8856A7952BD99016E3C9410D8C9C0009E679F2E1C2BBB4BED8EDA7D8B4A393D69C393DE2A07F227DD4FD778FAD306A916DAB6630A28A298C31466B54A694FC575943CB654B414A5C47C4824731E2290978D5FAE2C976936C977B901E8EB2927711850E8A1EEF22307D69252F26A564CEFF00B49BCE92D48FDB6E36D8F2E383DA30EB6A2D296D9E5DE091C41F2A52DD65353AEF32630D29A6A43EB752DA8E4A428938FAD6FBBDFEEB7E5B4BBACD5CA532084296948290798E0077573AB9A53B2895051582A006490078D7C7B4320E3B44FCE9526CD3651520B268ABA6A0483024DB5448CEE19C82BF54A72457A6E9B37D51698AECA760B6FB2CA0AD6A61E4A8800649C7034530B22D45789570E3EEB7F335F22E0BEADA4F91A7F8A467A47BE83E15E76A634E1C1F70F8D7A291C5AE9A9D8D7B0ED76D36BB544B6AACB2D0DC6692D02D3885E70319C1C73A6258AFE9BEB1DBB76DB8446F190A96C86F7BCB8E4D56C813E55B26B53613CA62432ADE42D38C83EB4E2D15B556EF1218B5DE582CCD748436F3292A43A7C40E293F4F2ABC277A62490C8A2B159AB8862951B68B75B0B712E224B2D5C53F765927DF79BEFC7E53D4F426999738D2E5C25330E7182EAB876C9682D491E00F0CF8F1A40EBEB55AECB75EC5ABFBD75B82944CB5BC527B33D0139E2AF0E953C9C1A3D22F5A24C8EC123032A3CBBAB7020F2391E15BA2C11739B1E0938F68790D820711BCA033F5AE68D5EC77C3970E15C2F32D31E1467A5BEAE4DB482A3F21D2A52C6C8F5A3CD05FD9496F3C92E486D27E59A7BE9ED3569D236AF6580D25A42465E7DC237DC3F894AFDB90AC2F58E986DEEC57A86D895E71BA6523FBD59E47FE5095FD10917671AA23DFE0439B6A931DB7E4210A7D1EF21033C4EF24903866AC9169B2D762520B653BBBA78E538C63E55861F664B21E8EF21D695C96DA82927D452FB5D6D43EC49E6C7608E275D490859DD2A4B4A3FC21238A95E1C878F2A46DCDD1BA479E06C32C2D2D6B9B3A649CA894A1B21B4A46780EA4FCC56ABEEC36D521ADFB24C7A23C07FA7215DA215EB8C8FAD781AD21B56D449126E17E55B82F88695254D91FC8D8C0A1CD27B58D3A3B7817D55C528E3D9A649709FE57471F4AAF99FF004CB42E351E8FBDE977B72E509686C9C21E4FBCDABC94387EF5E78A9712C80E1C9E80F414D381B4C45E62CBD2FACA10B7497DB5305F282942564606FA4F141CE0E797952CD68536B5217F12490AF31CEA7372AA66C523E69F7B33B169F896166E76B57B5497D387A4B806FA55D518FE1C1E9D79E4D2523E9BD473612AE10EC52DF86919ED529F8C0E6523991E40D31F64F11C5155CAC3770EC65283770B74B6F716D9E8A041209EE381919071D371C5A7612636EB358159AE810876D3F53BDA6748BAEC3514CD96B11E3A8734920E543C40071E24579F44681B6E9EB434E4B88D49BA3C90B92FBC80B2147894A73C80E5E278D7236C984CCD2AE3BFEDD370FBDCF2E68E7E80D320F33E751CADF068A10FB53B4C5B56AF262212DA2530878B6848012AC949C01DFBB9F9D462D17362CD798572928538DC5792E96D3CD653C401EB8A97ED817BDACDB4FE086D8FAA8D426CD6A3A8355DBACFBC52890F250B23984F3511E80D4A11F5219BA44D615A358ED764AA6CE99EC1670B3B8307B3183C908CFBE47551F9F4AF5DE767DB39D33B912F5A9673531C00E13BAA207794A50703CE9CD162B16F84DC68ACA5A61840436DA0704A40E005548BD5CA45DEF32EE129654F48754B513D38F01E4070F4AEBAA264F275B6FDB305C7BEE9DBBA6E165998DD71232DAB23805A738F250EA3A72AEF6C36CAD4D72E5A9660EDE507BB1696BE25248DE5ABCCEF019F3EFAD5616C9FF0F574F6FE2CEF3AA8FBDD3DF4EEE3F9F35AF613A8988F26769F7D610B92A1223E7F89406149F3C007D0D0074F52EDBBEC8D46F5BADF6B6E5468AE169D756E9495A81C2B7703000391939CD31B4E5FE16A7B247BAC1512D3C38A55F121438149F106AB56BBB1CAB06AFB846908210E3CA759591C16DA89208EFE783E20D3736131E533A3E53AF052587A6294CE7AE1290A23C3231E8680241AFB41C1D636A5E1B437736924C6918C1CFE051EA93F4E755CD80F34F3B0DE4143CD28A0A55CC107047A1AB7279555FD6FD89DA4DE7D9F1B9ED4BCE3F163DEFF00B66926AE2CD5D2C6DAA208369870C0C7B3B086FE490297D15B1A676EC23C61D9C5BEC52B5B63E10BC28938FF009209FE6358D9AEBF125B458AF2F80F21388D21C57FA807F0289EA0723D478F3F88529BD5FB6F6675BD41E83648A52A7D1C50A5614381EBEF2CE3BF74D47174690D8159AC0ACD748847B5B694635869D76D8EB9D93A141C61DC6771C1C8F910483E06A0D17556BDD22C2605F74BBD7669801089914925491C8920107D403DF4DAAC1AC693E8595A3575FD7A9750BD725C572265296C30E1CA91BA3183E39CD78ECD25FD35A82D1A95D654A862491BC3AEEE02C79EEAABEF51C77226A3B8B0F496E53A990BED1D6F3BAA51393CFC4E3D29A7A2B4BDBB516CB99B7DC992B69F7DD752A4F0536ADE202927A1E15CD19799146AD0C5833A2DCA135321BE87E3BC90A6DC41C850A5DDD7629619B7B76E7EDF222C57165D7632027033C4E147E11E87151FFF00C6DAEF4BBEBFF2B5F7B48CA39DD4BDD913FF00242BDD27C73515D6E35D4211A2EA6B9BCEFB58514474C90A07040C94A78733C2BA1493E13A247AF351C7BF983A0B46321D8ADAD285167E070A79241EA91CCABA919E993D0D47B227A1DAA04ED32E94DD203490E84AB74BEB1C77D27A2B3D3A8C75E732D13A12D9A420A16D35DA5C1D6C07E439C559C64A53DC9CFCFAD4AAA32C8EF43A889DB6ED3AD5706D169DA1D8D0B9118E0BEE460BC1EF5208CA4F7E39F753021EBED12DC34262DF6DECB084E10D85767BA3B827031F2AF6DE74C593502026ED6D625103016A4E163C943047CE92BB50D176BD2371B649B7B0E88127783AD29D2AC292412013C46527E9548E452D18D5135D5BB67B6468AB89A68AA7CD706EA1EECC86DB27A8CF151EE0063F4A80CDD0D3ECFA3D3A8AEFBE89F2E5A53D8B87DE4A141472AFCC4E0E3A0F3A72E9DD17A5ECA96A65A6D8D071690B4487097178232082ACE3D315AF5E69995AAACCCC18D25A8FD9BE1E5A9C04E40491818F3A9CF25E91A908BB346B5CC9E98F769CEC261CE01F4341C4A4FE6191C3C45582D17A6E1699B0B70E1A98782CEFAA4328DDEDF3C94789CF0F1C776290B61F6190B7ED53FB3684C012CCB50FF6EF0CEE927F01CEEABCC1E94E6D9DE96BE69A82A6AE972438CAC6510D037C327A90B3FA018ADC46489AD158ACD5C50A8D6BBD4A9D33A69F94850F6B7BEE62A7BD67AFA0C9F4A92D41B6A3A818B1D89296D0DAAE52829A8CB29054D24FC6B07A70C0F323BA964E91A8439254A2A512493924F326AC2ECF59EC7425A138C6F31BFF00D4A27F7AAF237411BD9DDEB8EEAB31A762A6169CB6C54AD2B0D456D2149E4AF747115C654E8D26F5AA7EDADB7D9ADABE2DB1D82549F0C9715F4A7252727FDCFF0088C8CB74E12B537BB9F16303EB54C7D62C871F3E3DF59AC0E559A98C14BADB6C44BFA25A7F1EF4798820F8282927F6A62D2EB6D935B8FA25B8C4FBF265A02478241513FA7CE9E1F9215F092E8394A99A12CAF2CE55EC88493DFBBEEFED5DD75412DA94790493F4AE0E828AB85A12CCCB80857B225641E9BDEF7EF5EBD513D36BD2F729AA38ECA32F77FE44607D48A57D3570AD8A3BCA51E849A766CAB5A7DAF6FF00B127BB99B111F74A51E2F343F529E47C307BE92206001DD5E9B7CF936AB8313E1BA5A911D616DA8743FD8F235B097960D596A6B35C4D29A9236A8B1B370630959F75E6B3C5A58E63F71E04576EBAD3B247CA88032780155B3596A273536A491394486527B28E83FC2D83C3D4F127CE9F7AC2E3F65692BA4D070A6E32820FE623753F522AB3018E1DD51CCFF43C4CD3E76617B4DDB4830C2D797E01F677013C7747141FE9E1E8690D5DDD21AAA4E93BB898D23B561C1B9219CE37D3E1DC474FFF006B9C72C6528B6C76D936BBCDA757C2202D85A1A59CF25A49520F8E4647A577646D92C0DC7DE8F1273CEE3820A12819F1564FE94B2D57AB2E1ABA6076684B6C3790CC647C280799F127BE9E32A7663563CB4BEAEB5EACB737260BE80F1482EC62A1DA34AEA08EEEE3C8D77307B8FCAAA608AA69C0E47794D287220E08F222BD2A9B7B5A77177896A477190B23E59A6A8BE3336593BDEA8B2E9D8EA76E97165820706F7B79C578048E26945DA4FDB16B76886571EC9031BC09E286C9C9C9FC6BC63872C7852FC420A5153AE2964F3F1F5A9468BD52EE8FBA990D33DAC6753B8FB20E37873041EF1FDE8B8C79D0A6CB148425B42508484A523000E400E94ABDAF6A76D486F4EC570294141D9641F871F0A3CFA9F4AF8BFED8FB786A62C509D61D58C191237728FF008A46727C4FCA960E38E3CEADD756A71C5A8A94B51C9513CC935318F9A28A2B00636CD6DFAAECD3A3DCE2DB1C91699C901EDD751EF273C16015734FE9914EAE3E1503D8F5C7DAF46988A56550A42DBC7E557BC3F53F2A9ED76415224FA2F76CD70F66D28C4249C2A6494823BD281BC7EBBB490A636DA6E3DBEA285012ACA62C6DF50EE52CFF00648A5CD73E4772291E05145153342B5B921B68E16AC1EEC56CAF9536851CA90924778AD55FB035A65B0A380B03CC62B766B52E3B2B182D8F4E15AA3EF32FA982494E32934F49AD196CF551451533428A28A0028A283C050032B62971EC6F770B7295812180EA47E641C1FA2BE94E7CD577D1E6569BD79685CB41692FA90013F0ADB75380A07A8E23E5561BDEEEAEAC4FEB44E5D14ACE8E7B5DEB6B85F26A94DD9848DC694382A4A5184809EE4F0E2AF9782BA404894E840C2438A091DC327156A5C09663AB7404A508380060000555351DE515779CD4F2248D8B31451454470A28A2800A31451400514514005145140051451400E6D2F6585ADB6690187CF6732DEA5B6C4948CA995A5594F98C6EE454DF7F50FF00E9B77FF22FFF00AD2F762170CB374B6A95F0A90FA0798293FA0A6BD76436AC93E9E4B9AFB3B5CB5FE16167FEA6AAEC3892273A962334A75D282ADD4F3C049513E8013566F512FB3D37735E71BB0DD3FF0043506D9368F4C1B3AAF539A1ED13DBDD692A1F0327F7573F2C52CE3E9A46A7484C03919A2BD57582AB65DA64058C18CFADAF92881F4C5796B95940A28A2800A28A2800A28A2800A28A2803D56CB6C8BBDC1B811021521EC86D2B504EF9033BA09E1938E15F1360CBB74A5459B19D8CFA3E26DD4949AF88D25E8729A951D5B8F32B0E36A1D140E47E95635B8D65D75A6A24B9B09A92CC9682D3BC3DE6D5D40573041C8E1DD548414918DD0A2D935C3D8B5CB0D156133195B27CF1BC3EA9FAD3F37877D2AA4ECAE6586FB0EF1A7A47B5371A421D319E2038003C40572570CF3C7AD357753DD57C69A54C9B354B8ACCE88EC5909DF65E414389CFC493C08AD8942508094A425206001C80AFBAC550C2BFED56DDEC1AEA4B813BA898DA1F1E646E9FAA6A1D4DADB75BB2D5AEE694FC2A5C759F31BC9FD15503D21A4A66ADBA88AC65B8CDE15224638369F0EF51E83F6AE49C7ED48A27A38C224930CCC0C39ECC970345EDD3BA16464273DF8AD5565D5A4ED074C2B4EA6304C12DEE6E8F8B3F8F3F8B3C73DF55EF50D865E9BBD3F6C9832A6CE50B0301C41E4A1E7F439144F1F904ECE6515BE14291719CCC288D175F7D610DA075269FB6ED9D5958D22DD866C74C827EF1D7C7059748E2A49E631C8780AC841C8D6E8AF4738E0327A0A91EAAD1772D2A63BB2125D8B21092979238256464A15DC471C778AEF3BB31B95A758DB23A92A976C7E52712529F8520EF10B1D0E0791FA53A2E16F897582EC29CC25F8EF2775685F223F63E34F1C769D8AE4557A9AECE34546D5AEDC173CBA88CC361085B6AC10E2B883E3800F0F115E4D73A1A56919BDA2379FB6BCAFB97C8E293F817DC7C7AD37B67164FB1346C36DC4EEBF247B43BDF957103D13814421F6A66B7A14FAAB66B7AD39BF21A419F053C7B7653EF207E74F31E6323CAA5BB16BF769165D89D5E4B47DA18C9FE13C163D0E0FF0035353151D73455ADBD431EFB6F47B0CD6944B8591843E9230A0A4F2E3DE3AF7D51429DA16F448AB358159AA8A1451450047B5BE9B56A9D3ABB6B4E21A77B66DC42D7C9385713FD2555EDD3F6083A6ED4D5BA037BADA38A967E2715D54A3D49A28ACA5760752A21B43D1C9D5566DF8E948B8C50551D5CB7FBD04F71E9DC71E34514356A80E1ECA345AADB14DFAE2C944B7D2531DB58C169BEA48E855FA79D32A8A2B22A91AC3145145318689B062DC62391263087D874616DAC642856F4A424000600E94514019A28A2800A28A2803FFFD9")
+
+ val writeTask=WriteIDCardTask(ctx.card, cardProtocol!!.reader,App.localStorage, App.pinStorage, idCardData, ctx.card.issuer.privateDataKey,
+ object: CardProtocol.Notifications{
+ override fun onReadProgress(cardProtocol: CardProtocol?, progress: Int) {
+ LOG.i("WriteIDCardTask","onReadProgress: ${progress}%")
+ }
+ override fun onReadWait(msec: Int) {
+ }
+ override fun onReadStart(cardProtocol: CardProtocol?) {
+ LOG.i("WriteIDCardTask","onReadStart")
+ rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE }
+
+ }
+ override fun onReadCancel() {
+ LOG.i("WriteIDCardTask","onReadCancel")
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+
+ }
+ override fun onReadFinish(cardProtocol: CardProtocol?) {
+ LOG.i("WriteIDCardTask","onReadFinish")
+ rlProgressBar?.postDelayed({
+ try {
+ rlProgressBar?.visibility = View.GONE
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }, 500)
+
+ }
+ override fun onReadAfterRequest() {
+ }
+ override fun onReadBeforeRequest(timeout: Int) {
+ }
+ })
+ writeTask.start()
+ return
+ }
+ //TODO end
(activity as MainActivity).nfcManager.ignoreTag(isoDep.tag)
return
}
diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
index c65b0da0e2..3d93e06d50 100644
--- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
+++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
@@ -10,6 +10,7 @@ import com.tangem.wallet.cardano.CardanoEngine
import com.tangem.wallet.ducatus.DucatusEngine
import com.tangem.wallet.eos.EosEngine
import com.tangem.wallet.eth.EthEngine
+import com.tangem.wallet.ethID.EthIdEngine
import com.tangem.wallet.ltc.LtcEngine
import com.tangem.wallet.matic.MaticTokenEngine
import com.tangem.wallet.nftToken.NftTokenEngine
@@ -19,7 +20,7 @@ import com.tangem.wallet.token.TokenEngine
import com.tangem.wallet.tezos.TezosEngine
import com.tangem.wallet.xlm.XlmAssetEngine
import com.tangem.wallet.xlm.XlmEngine
-import com.tangem.wallet.xlmtag.XlmTagEngine
+import com.tangem.wallet.xlmTag.XlmTagEngine
import com.tangem.wallet.xrp.XrpEngine
/**
@@ -39,6 +40,7 @@ object CoinEngineFactory {
Blockchain.Bitcoin, Blockchain.BitcoinTestNet -> BtcEngine()
Blockchain.BitcoinCash -> BtcCashEngine()
Blockchain.Ethereum, Blockchain.EthereumTestNet -> EthEngine()
+ Blockchain.EthereumId -> EthIdEngine()
Blockchain.Token -> TokenEngine()
Blockchain.NftToken -> NftTokenEngine()
Blockchain.Litecoin -> LtcEngine()
@@ -67,6 +69,8 @@ object CoinEngineFactory {
BtcEngine(context)
else if (Blockchain.Ethereum == context.blockchain || Blockchain.EthereumTestNet == context.blockchain)
EthEngine(context)
+ else if (Blockchain.EthereumId == context.blockchain)
+ EthIdEngine(context)
else if (Blockchain.Token == context.blockchain)
TokenEngine(context)
else if (Blockchain.NftToken == context.blockchain)
diff --git a/app/src/main/java/com/tangem/wallet/TangemContext.java b/app/src/main/java/com/tangem/wallet/TangemContext.java
index 07cb229329..df164a4616 100644
--- a/app/src/main/java/com/tangem/wallet/TangemContext.java
+++ b/app/src/main/java/com/tangem/wallet/TangemContext.java
@@ -36,6 +36,9 @@ public class TangemContext {
return Blockchain.Token;
}
}
+ if (blockchain == Blockchain.Ethereum && card.isIDCard()) {
+ return Blockchain.EthereumId;
+ }
if ((blockchain == Blockchain.Rootstock) && card.isToken()) {
return Blockchain.RootstockToken;
}
@@ -55,14 +58,16 @@ public class TangemContext {
public String getBlockchainName() {
Blockchain blockchain = getBlockchain();
if (blockchain == Blockchain.Token || blockchain == Blockchain.RootstockToken) {
- String token = card.getTokenSymbol();
- return token + "
" + getBlockchain().getOfficialName() + " smart contract token";
+ return card.getTokenSymbol()+ "
" + getBlockchain().getOfficialName() + " smart contract token";
}
if (blockchain == Blockchain.NftToken) {
- return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " NFT token";
+ return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " non-fungible token";
}
if (blockchain == Blockchain.StellarAsset) {
- return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset";
+ return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset";
+ }
+ if (blockchain == Blockchain.StellarTag) {
+ return "TANGEM TAG
"+ getBlockchain().getOfficialName() + " non-fungible token ";
}
return blockchain.getOfficialName();
}
diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java
index 2acdefb4bc..f5614c7d03 100644
--- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java
+++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java
@@ -275,11 +275,11 @@ public class BtcCashEngine extends CoinEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java
index 9d4a9be074..48ae73ef42 100644
--- a/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java
+++ b/app/src/main/java/com/tangem/wallet/binance/BinanceEngine.java
@@ -247,11 +247,11 @@ public class BinanceEngine extends CoinEngine {
}
}
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
return true;
} catch (Exception e) {
diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java
index fd49ed7ec4..49da3cd0a5 100644
--- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java
+++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java
@@ -310,11 +310,11 @@ public class BtcEngine extends CoinEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java
index 3b7792df59..e51226a99b 100644
--- a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java
+++ b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java
@@ -251,11 +251,11 @@ public class CardanoEngine extends CoinEngine {
}
}
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
return true;
} catch (Exception e) {
diff --git a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java
index 17e8b9789c..f79127ae7f 100644
--- a/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java
+++ b/app/src/main/java/com/tangem/wallet/ducatus/DucatusEngine.java
@@ -263,11 +263,11 @@ public class DucatusEngine extends BtcEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java
index 47ee91442f..f667cfd0ef 100644
--- a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java
+++ b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java
@@ -293,11 +293,11 @@ public class EosEngine extends CoinEngine {
}
}
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
+// }
return true;
diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java
index 839d066aa1..76e23e4771 100644
--- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java
+++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java
@@ -334,11 +334,11 @@ public class EthEngine extends CoinEngine {
}
}
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
+// }
return true;
diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java
new file mode 100644
index 0000000000..6cdf9fcf0b
--- /dev/null
+++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdData.java
@@ -0,0 +1,68 @@
+package com.tangem.wallet.ethID;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.tangem.wallet.CoinData;
+
+public class EthIdData extends CoinData {
+ private boolean hasApprovalTx = false;
+ private Long approvalAddressNonce;
+ private byte[] CKDpub = null;
+
+ public boolean isHasApprovalTx() {
+ return hasApprovalTx;
+ }
+
+ public void setHasApprovalTx(boolean hasApprovalTx) {
+ this.hasApprovalTx = hasApprovalTx;
+ }
+
+ public long getApprovalAddressNonce() {
+ return approvalAddressNonce;
+ }
+
+ public void setApprovalAddressNonce(long approvalAddressNonce) {
+ this.approvalAddressNonce = approvalAddressNonce;
+ }
+
+ public byte[] getCKDpub() {
+ return CKDpub;
+ }
+
+ public void setCKDpub(byte[] CKDpub) {
+ this.CKDpub = CKDpub;
+ }
+
+ @Override
+ public void clearInfo() {
+ super.clearInfo();
+ hasApprovalTx = false;
+ approvalAddressNonce = null;
+ }
+
+ @Override
+ public void loadFromBundle(Bundle B) {
+ super.loadFromBundle(B);
+
+ if (B.containsKey("ApprovalAddressNonce")) {
+ approvalAddressNonce = B.getLong("ApprovalAddressNonce");
+ }
+ if (B.containsKey("CKDpub")) CKDpub = B.getByteArray("CKDpub");
+ hasApprovalTx = B.getBoolean("HasApprovalTx");
+ }
+
+ @Override
+ public void saveToBundle(Bundle B) {
+ super.saveToBundle(B);
+ try {
+ if (approvalAddressNonce != null) B.putLong("ApprovalAddressNonce", approvalAddressNonce);
+ if (CKDpub != null) B.putByteArray("CKDpub", CKDpub);
+ B.putBoolean("HasApprovalTx", hasApprovalTx);
+
+ } catch (Exception e) {
+ Log.e("Can't save to bundle ", e.getMessage());
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java
new file mode 100644
index 0000000000..ebf42f4e21
--- /dev/null
+++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java
@@ -0,0 +1,534 @@
+package com.tangem.wallet.ethID;
+
+import android.net.Uri;
+import android.text.InputFilter;
+import android.util.Log;
+
+import com.tangem.data.Blockchain;
+import com.tangem.data.network.ServerApiBlockcypher;
+import com.tangem.data.network.ServerApiInfura;
+import com.tangem.data.network.model.BlockcypherFee;
+import com.tangem.data.network.model.BlockcypherResponse;
+import com.tangem.data.network.model.BlockcypherTx;
+import com.tangem.data.network.model.BlockcypherTxref;
+import com.tangem.data.network.model.InfuraResponse;
+import com.tangem.tangem_card.data.TangemCard;
+import com.tangem.tangem_card.reader.CardProtocol;
+import com.tangem.tangem_card.tasks.SignTask;
+import com.tangem.tangem_card.util.Util;
+import com.tangem.util.CryptoUtil;
+import com.tangem.util.DecimalDigitsInputFilter;
+import com.tangem.wallet.BTCUtils;
+import com.tangem.wallet.BalanceValidator;
+import com.tangem.wallet.CoinData;
+import com.tangem.wallet.CoinEngine;
+import com.tangem.wallet.ECDSASignatureETH;
+import com.tangem.wallet.EthTransaction;
+import com.tangem.wallet.Keccak256;
+import com.tangem.wallet.R;
+import com.tangem.wallet.TangemContext;
+import com.tangem.wallet.eth.EthData;
+
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.SignatureDecodeException;
+import org.bitcoinj.crypto.ChildNumber;
+import org.bitcoinj.crypto.DeterministicKey;
+import org.bitcoinj.crypto.HDKeyDerivation;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.Arrays;
+
+public class EthIdEngine extends CoinEngine {
+
+ private static final String TAG = com.tangem.wallet.eth.EthEngine.class.getSimpleName();
+ public EthIdData coinData = null;
+ private byte[] approvalPubKey = Util.hexToBytes("04EAD74FEEE4061044F46B19EB654CEEE981E9318F0C8FE99AF5CDB9D779D2E52BB51EA2D14545E0B323F7A90CF4CC72753C973149009C10DB2D83DCEC28487729"); //TODO
+ public String approvalAddress = calculateAddress(approvalPubKey); //TODO
+
+ public EthIdEngine(TangemContext ctx) throws Exception {
+ super(ctx);
+ if (ctx.getCoinData() == null) {
+ coinData = new EthIdData();
+ ctx.setCoinData(coinData);
+ } else if (ctx.getCoinData() instanceof EthIdData) {
+ coinData = (EthIdData) ctx.getCoinData();
+ } else {
+ throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName());
+ }
+ }
+
+ public EthIdEngine() {
+ super();
+ }
+
+ private static int getDecimals() {
+ return 18;
+ }
+
+ private int getChainIdNum() {
+ return EthTransaction.ChainEnum.Mainnet.getValue();
+ }
+
+ @Override
+ public boolean awaitingConfirmation() {
+ return false;
+ }
+
+ @Override
+ public Amount getBalance() {
+ return null;
+ }
+
+ @Override
+ public String getBalanceHTML() {
+ if (coinData != null) {
+ if (coinData.isHasApprovalTx()) {
+ return "VERIFIED";
+ } else {
+ return "NOT REGISTERED";
+ }
+ } else {
+ return "NOT REGISTERED";
+ }
+ }
+
+ @Override
+ public String getBalanceCurrency() {
+ return Blockchain.Ethereum.getCurrency();
+ }
+
+ @Override
+ public boolean isBalanceNotZero() {
+ return true;
+ }
+
+ @Override
+ public String getFeeCurrency() {
+ return Blockchain.Ethereum.getCurrency();
+ }
+
+ public boolean isNeedCheckNode() {
+ return false;
+ }
+
+
+ @Override
+ public CoinData createCoinData() {
+ return new EthIdData();
+ }
+
+ @Override
+ public String getUnspentInputsDescription() {
+ return "";
+ }
+
+ @Override
+ public boolean validateAddress(String address) {
+ if (address == null || address.isEmpty()) {
+ return false;
+ }
+
+ if (!address.startsWith("0x") && !address.startsWith("0X")) {
+ return false;
+ }
+
+ if (address.length() != 42) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public String getBalanceEquivalent() {
+ Amount balance = getBalance();
+ if (balance == null) return "";
+ return balance.toEquivalentString(coinData.getRate());
+ }
+
+ @Override
+ public Amount convertToAmount(InternalAmount internalAmount) {
+ BigDecimal d = internalAmount.divide(new BigDecimal("1000000000000000000"), getDecimals(), RoundingMode.DOWN);
+ return new Amount(d, ctx.getBlockchain().getCurrency());
+ }
+
+ @Override
+ public Amount convertToAmount(String strAmount, String currency) {
+ return new Amount(strAmount, currency);
+ }
+
+ @Override
+ public InternalAmount convertToInternalAmount(Amount amount) {
+ return new InternalAmount(amount.multiply(new BigDecimal("1000000000000000000")), "wei");
+ }
+
+ @Override
+ public InternalAmount convertToInternalAmount(byte[] bytes) {
+ //throw new Exception("Not implemented");
+ return null;
+ }
+
+
+ @Override
+ public byte[] convertToByteArray(InternalAmount amount) throws Exception {
+ throw new Exception("Not implemented");
+ }
+
+ @Override
+ public boolean hasBalanceInfo() {
+ return true;
+ }
+
+ @Override
+ public Uri getShareWalletUri() {
+ if (ctx.getCard().getDenomination() != null) {
+ return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18");
+ } else {
+ return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());
+ }
+ }
+
+ @Override
+ public Uri getWalletExplorerUri() {
+ if (ctx.getBlockchain() == Blockchain.EthereumTestNet)
+ return Uri.parse("https://rinkeby.etherscan.io/address/" + ctx.getCoinData().getWallet());
+ else
+ return Uri.parse("https://etherscan.io/address/" + ctx.getCoinData().getWallet());
+ }
+
+ @Override
+ public boolean isExtractPossible() {
+ if (!hasBalanceInfo()) {
+ ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data);
+ } else if (!isBalanceNotZero()) {
+ ctx.setMessage(R.string.general_wallet_empty);
+ } else if (awaitingConfirmation()) {
+ ctx.setMessage(R.string.loaded_wallet_message_wait);
+ } else {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public InputFilter[] getAmountInputFilters() {
+ return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
+ }
+
+ @Override
+ public boolean checkNewTransactionAmount(Amount amount) {
+ return true;
+ }
+
+ @Override
+ public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) {
+ return true;
+ }
+
+ @Override
+ public boolean validateBalance(BalanceValidator balanceValidator) {
+ if (coinData != null && coinData.isHasApprovalTx()) {
+ balanceValidator.setScore(100);
+ balanceValidator.setFirstLine(R.string.id_verified);
+ balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance);
+ return false;
+ } else {
+ balanceValidator.setScore(0);
+ balanceValidator.setFirstLine(R.string.id_not_registered);
+ balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance);
+ return false;
+ }
+ }
+
+ @Override
+ public String evaluateFeeEquivalent(String fee) {
+ try {
+ Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency());
+ return feeValue.toEquivalentString(coinData.getRate());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ @Override
+ public String calculateAddress(byte[] pkUncompressed) {
+ Keccak256 kec = new Keccak256();
+ int lenPk = pkUncompressed.length;
+ if (lenPk < 2) {
+ throw new IllegalArgumentException("Uncompress public key length is invalid");
+ }
+ byte[] cleanKey = new byte[lenPk - 1];
+ for (int i = 0; i < cleanKey.length; ++i) {
+ cleanKey[i] = pkUncompressed[i + 1];
+ }
+ byte[] r = kec.digest(cleanKey);
+
+ byte[] address = new byte[20];
+ for (int i = 0; i < 20; ++i) {
+ address[i] = r[i + 12];
+ }
+
+ return String.format("0x%s", BTCUtils.toHex(address));
+ }
+
+ @Override
+ public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) {
+
+ final EthTransaction tx = constructIdTxForSign(coinData.getApprovalAddressNonce());
+
+ return new SignTask.TransactionToSign() {
+ @Override
+ public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
+ return signingMethod == TangemCard.SigningMethod.Sign_Hash;
+ }
+
+ @Override
+ public byte[][] getHashesToSign() {
+ byte[][] hashesForSign = new byte[1][];
+ hashesForSign[0] = tx.getRawHash();
+ return hashesForSign;
+ }
+
+ @Override
+ public byte[] getRawDataToSign() throws Exception {
+ throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
+ }
+
+ @Override
+ public String getHashAlgToSign() throws Exception {
+ throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
+ }
+
+ @Override
+ public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
+ throw new Exception("Transaction validation by issuer not supported in this version");
+ }
+
+ @Override
+ public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
+ byte[] for_hash = tx.getRawHash();
+ BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
+ BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
+ s = CryptoUtil.toCanonicalised(s);
+
+ boolean f = ECKey.verify(for_hash, new ECKey.ECDSASignature(r, s), approvalPubKey);
+
+ if (!f) {
+ Log.e(this.getClass().getSimpleName() + "-CHECK", "sign Failed.");
+ }
+
+ tx.signature = new ECDSASignatureETH(r, s);
+ int v = tx.BruteRecoveryID2(tx.signature, for_hash, approvalPubKey);
+ if (v != 27 && v != 28) {
+ Log.e(this.getClass().getSimpleName(), "invalid v");
+ throw new Exception("Error in " + this.getClass().getSimpleName() + " - invalid v");
+ }
+ tx.signature.v = (byte) v;
+ Log.e(this.getClass().getSimpleName(), " V: " + String.valueOf(v));
+
+ byte[] txForSend = tx.getEncoded();
+ notifyOnNeedSendTransaction(txForSend);
+ return txForSend;
+ }
+ };
+ }
+
+ @Override
+ public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
+
+ final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
+
+ ServerApiBlockcypher.ResponseListener blockcypherListener = new ServerApiBlockcypher.ResponseListener() {
+ @Override
+ public void onSuccess(String method, BlockcypherResponse blockcypherResponse) {
+ Log.i(TAG, "onSuccess: " + method);
+ try {
+ //TODO: change request logic, 2000 tx max
+ if (blockcypherResponse.getTxrefs() != null) {
+ for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) {
+ serverApiBlockcypher.requestData("ETH", ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txref.getTx_hash());
+ }
+
+ if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
+ blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
+ } else {
+ blockchainRequestsCallbacks.onProgress();
+ }
+ } else {
+ blockchainRequestsCallbacks.onComplete(!ctx.hasError());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "FAIL BLOCKCYPHER_ADDRESS Exception");
+ }
+ }
+
+ public void onSuccess(String method, BlockcypherFee blockcypherFee) {
+ Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
+ }
+
+ @Override
+ public void onFail(String method, String message) {
+ Log.i(TAG, "onFail: " + method + " " + message);
+ }
+ };
+
+ ServerApiBlockcypher.TxResponseListener txListener = new ServerApiBlockcypher.TxResponseListener() {
+ @Override
+ public void onSuccess(BlockcypherTx response) {
+ Log.i(TAG, "onSuccess: BlockcypherTx");
+ try {
+ String address = approvalAddress;
+ if (address.startsWith("0x") || address.startsWith("0X")) {
+ address = address.substring(2);
+ }
+ if (response.getAddesses().contains(address)) {
+ coinData.setHasApprovalTx(true);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "onFail: BlockcypherTx" + response);
+ }
+ if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
+ blockchainRequestsCallbacks.onComplete(!ctx.hasError());
+ } else {
+ blockchainRequestsCallbacks.onProgress();
+ }
+ }
+
+ @Override
+ public void onFail(String message) {
+ Log.i(TAG, "onFail: BlockcypherTx " + message);
+ if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
+ blockchainRequestsCallbacks.onComplete(!ctx.hasError());//serverApiElectrum.isErrorOccurred(), serverApiElectrum.getError());
+ } else {
+ blockchainRequestsCallbacks.onProgress();
+ }
+ }
+ };
+
+ serverApiBlockcypher.setResponseListener(blockcypherListener);
+ serverApiBlockcypher.setTxResponseListener(txListener);
+
+ serverApiBlockcypher.requestData("ETH", ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
+ requestApprovalAddressNonce();
+ }
+
+ @Override
+ public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
+ blockchainRequestsCallbacks.onComplete(true);
+ }
+
+ @Override
+ public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) {
+
+ String txStr = String.format("0x%s", BTCUtils.toHex(txForSend));
+
+ ServerApiInfura serverApiInfura = new ServerApiInfura();
+ // request requestData eth gasPrice listener
+ ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
+ @Override
+ public void onSuccess(String method, InfuraResponse infuraResponse) {
+ if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
+ if (infuraResponse.getResult() == null || infuraResponse.getResult().isEmpty()) {
+ ctx.setError("Rejected by node: " + infuraResponse.getError());
+ blockchainRequestsCallbacks.onComplete(false);
+ } else {
+ ctx.setError(null);
+ blockchainRequestsCallbacks.onComplete(true);
+ }
+ }
+ }
+
+ @Override
+ public void onFail(String method, String message) {
+ if (method.equals(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION)) {
+ ctx.setError(message);
+ blockchainRequestsCallbacks.onComplete(false);
+ }
+ }
+ };
+
+ serverApiInfura.setResponseListener(responseListener);
+
+ serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_SEND_RAW_TRANSACTION, 67, coinData.getWallet(), "", txStr);
+ }
+
+ public int pendingTransactionTimeoutInSeconds() {
+ return 10;
+ }
+
+ @Override
+ public boolean needMultipleLinesForBalance() {
+ return true;
+ }
+
+ @Override
+ public void defineWallet() throws CardProtocol.TangemException {
+ try {
+ String wallet = calculateAddress(calculateCKDpub(ctx.getCard().getIdHash()));
+ ctx.getCoinData().setWallet(wallet);
+ } catch (Exception e) {
+ ctx.getCoinData().setWallet("ERROR");
+ throw new CardProtocol.TangemException("Can't define wallet address");
+ }
+
+ }
+
+ private byte[] calculateCKDpub(byte[] idHash) {
+ DeterministicKey masterPubKey = HDKeyDerivation.createMasterPubKeyFromBytes(ctx.getCard().getWalletPublicKeyRar(), idHash);
+ DeterministicKey childPubKey = HDKeyDerivation.deriveChildKey(masterPubKey, new ChildNumber(1, false));
+ return childPubKey.getPubKey();
+ }
+
+ private EthTransaction constructIdTxForSign(long nonce) {
+ BigInteger nonceValue = BigInteger.valueOf(nonce);
+
+ BigInteger gasPrice = BigInteger.valueOf(10000000000L);
+ BigInteger gasLimit = BigInteger.valueOf(21000);
+
+ BigInteger weiAmount = gasPrice.multiply(gasLimit).add(BigInteger.ONE);
+
+ Integer chainId = this.getChainIdNum();
+
+ String to = coinData.getWallet();
+
+ if (to.startsWith("0x") || to.startsWith("0X")) {
+ to = to.substring(2);
+ }
+
+ return EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
+ }
+
+ private boolean checkSignature(byte[] signature, long nonce) throws SignatureDecodeException {
+ byte[] hashForSign = constructIdTxForSign(nonce).getRawHash();
+// new Secp256k1Context();
+ return ECKey.verify(hashForSign, signature, coinData.getCKDpub());
+ }
+
+ private void requestApprovalAddressNonce() {
+ final ServerApiInfura serverApiInfura = new ServerApiInfura();
+
+ ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
+ @Override
+ public void onSuccess(String method, InfuraResponse infuraResponse) {
+ String pending = infuraResponse.getResult();
+ pending = pending.substring(2);
+// BigInteger count = new BigInteger(pending, 16);
+ Long count = Long.valueOf(pending, 16);
+ coinData.setApprovalAddressNonce(count);
+ }
+
+ @Override
+ public void onFail(String method, String message) {
+ Log.e(TAG, "onFail: " + method + " " + message);
+ }
+ };
+ serverApiInfura.setResponseListener(responseListener);
+
+ serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, approvalAddress, "", "");
+ }
+}
diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java
index 7402e658d9..45e9749077 100644
--- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java
+++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java
@@ -275,11 +275,11 @@ public class LtcEngine extends BtcEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/tezos/TezosEngine.kt b/app/src/main/java/com/tangem/wallet/tezos/TezosEngine.kt
index 5ad3c85cf2..2160bd4b86 100644
--- a/app/src/main/java/com/tangem/wallet/tezos/TezosEngine.kt
+++ b/app/src/main/java/com/tangem/wallet/tezos/TezosEngine.kt
@@ -161,18 +161,17 @@ class TezosEngine : CoinEngine {
balanceValidator.setSecondLine(R.string.empty_string)
}
}
- if (ctx.card.offlineBalance != null &&
- !coinData!!.isBalanceReceived &&
- ctx.card.remainingSignatures ==
- ctx.card.maxSignatures &&
- balance!!.notZero()
- ) {
- balanceValidator.setScore(80)
- balanceValidator.firstLine = R.string.balance_validator_first_line_verified_offline
- balanceValidator.setSecondLine(
- R.string.balance_validator_second_line_internet_to_get_balance
- )
- }
+// if (ctx.card.offlineBalance != null &&
+// !coinData!!.isBalanceReceived &&
+// ctx.card.remainingSignatures ==
+// ctx.card.maxSignatures
+// ) {
+// balanceValidator.setScore(80)
+// balanceValidator.firstLine = R.string.balance_validator_first_line_verified_offline
+// balanceValidator.setSecondLine(
+// R.string.balance_validator_second_line_internet_to_get_balance
+// )
+// }
return true
} catch (e: java.lang.Exception) {
e.printStackTrace()
diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java
index 626b41ff00..394806cda9 100644
--- a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java
+++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java
@@ -275,11 +275,11 @@ public class XlmAssetEngine extends CoinEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getXlmBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java
index c707517abc..3e2f6c8f77 100644
--- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java
+++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java
@@ -243,11 +243,11 @@ public class XlmEngine extends CoinEngine {
// return;
// }
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
// if(card.getFailedBalanceRequestCounter()!=0) {
// score -= 5 * card.getFailedBalanceRequestCounter();
diff --git a/app/src/main/java/com/tangem/wallet/xlmtag/XlmTagData.kt b/app/src/main/java/com/tangem/wallet/xlmTag/XlmTagData.kt
similarity index 99%
rename from app/src/main/java/com/tangem/wallet/xlmtag/XlmTagData.kt
rename to app/src/main/java/com/tangem/wallet/xlmTag/XlmTagData.kt
index bdd8f4e3af..e8287ccd86 100644
--- a/app/src/main/java/com/tangem/wallet/xlmtag/XlmTagData.kt
+++ b/app/src/main/java/com/tangem/wallet/xlmTag/XlmTagData.kt
@@ -1,4 +1,4 @@
-package com.tangem.wallet.xlmtag
+package com.tangem.wallet.xlmTag
import android.os.Bundle
import android.util.Log
diff --git a/app/src/main/java/com/tangem/wallet/xlmtag/XlmTagEngine.kt b/app/src/main/java/com/tangem/wallet/xlmTag/XlmTagEngine.kt
similarity index 78%
rename from app/src/main/java/com/tangem/wallet/xlmtag/XlmTagEngine.kt
rename to app/src/main/java/com/tangem/wallet/xlmTag/XlmTagEngine.kt
index 4fe5de60ca..a21f69f934 100644
--- a/app/src/main/java/com/tangem/wallet/xlmtag/XlmTagEngine.kt
+++ b/app/src/main/java/com/tangem/wallet/xlmTag/XlmTagEngine.kt
@@ -1,4 +1,4 @@
-package com.tangem.wallet.xlmtag
+package com.tangem.wallet.xlmTag
import android.net.Uri
import android.os.StrictMode
@@ -53,12 +53,18 @@ class XlmTagEngine : CoinEngine {
}
override fun getBalanceHTML(): String {
- return if (hasBalanceInfo() && isBalanceNotZero && coinData!!.fundsFromTrustedSource) {
- ctx.getString(R.string.tag_genuine)
- } else if (coinData!!.fundsSentToTrustedSource) {
- ctx.getString(R.string.tag_claimed)
+ return if (hasBalanceInfo()) {
+ if (coinData!!.fundsFromTrustedSource) {
+// if (coinData!!.fundsSentToTrustedSource) { //TODO: shut down for example tags to appear genuine, turn on for production
+// ctx.getString(R.string.tag_claimed)
+// } else {
+ ctx.getString(R.string.tag_genuine)
+// }
+ } else {
+ ctx.getString(R.string.tag_not_genuine)
+ }
} else {
- ctx.getString(R.string.tag_not_genuine)
+ ""
}
}
@@ -72,7 +78,7 @@ class XlmTagEngine : CoinEngine {
}
override fun hasBalanceInfo(): Boolean {
- return if (coinData == null) false else coinData!!.getBalance() != null || coinData!!.isError404
+ return if (coinData == null) false else coinData!!.isBalanceReceived
}
override fun isExtractPossible(): Boolean {
@@ -130,40 +136,26 @@ class XlmTagEngine : CoinEngine {
}
override fun validateBalance(balanceValidator: BalanceValidator): Boolean {
- return try {
- if (ctx.card.offlineBalance == null && !ctx.coinData.isBalanceReceived || !ctx.coinData.isBalanceReceived && ctx.card.remainingSignatures != ctx.card.maxSignatures) {
- if (coinData!!.isError404) {
- balanceValidator.setScore(0)
- balanceValidator.firstLine = R.string.balance_validator_first_line_no_account
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_instruction)
- } else {
- balanceValidator.setScore(0)
- balanceValidator.firstLine = R.string.balance_validator_first_line_unknown_balance
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance)
- return false
- }
+ try {
+ if (!hasBalanceInfo()) {
+ balanceValidator.setScore(0)
+ balanceValidator.firstLine = R.string.balance_validator_first_line_no_connection
+ balanceValidator.setSecondLine(R.string.balance_validator_second_line_authenticity_not_verified)
+ return false
}
-
- if (coinData!!.isBalanceReceived && coinData!!.isBalanceEqual) {
+ if (coinData!!.fundsFromTrustedSource) {
balanceValidator.setScore(100)
- balanceValidator.firstLine = R.string.balance_validator_first_line_verified_balance
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain)
- if (coinData!!.getBalance()!!.isZero) {
- balanceValidator.firstLine = R.string.balance_validator_first_line_empty_wallet
- balanceValidator.setSecondLine(R.string.empty_string)
- }
+ balanceValidator.firstLine = R.string.balance_validator_first_line_verified_in_blockchain
+ balanceValidator.setSecondLine(R.string.empty_string)
+ } else {
+ balanceValidator.setScore(0)
+ balanceValidator.firstLine = R.string.balance_validator_first_line_authenticity
+ balanceValidator.setSecondLine(R.string.empty_string)
}
-
- if (ctx.card.offlineBalance != null && !coinData!!.isBalanceReceived && ctx.card.remainingSignatures == ctx.card.maxSignatures && coinData!!.getBalance()!!.notZero()) {
- balanceValidator.setScore(80)
- balanceValidator.firstLine = R.string.balance_validator_first_line_verified_offline
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance)
- }
-
- true
+ return true
} catch (e: Exception) {
e.printStackTrace()
- false
+ return false
}
}
@@ -310,7 +302,7 @@ class XlmTagEngine : CoinEngine {
val accountKeyPair = KeyPair.fromAccountId(coinData!!.wallet)
try {
- var operationsPage = server.payments().forAccount(accountKeyPair).order(RequestBuilder.Order.DESC).execute()
+ var operationsPage = server.payments().forAccount(accountKeyPair).limit(200).order(RequestBuilder.Order.DESC).execute()
Log.e("Stellar", operationsPage.records.toString())
operations.addAll(operationsPage.records)
@@ -324,6 +316,8 @@ class XlmTagEngine : CoinEngine {
}
parsePayments(blockchainRequestsCallbacks)
+
+ CoroutineScope(Dispatchers.Main).launch { blockchainRequestsCallbacks.onComplete(!ctx.hasError()) }
} catch (e: Exception) {
if (e.message != null) {
ctx.error = e.message
@@ -342,53 +336,11 @@ class XlmTagEngine : CoinEngine {
Log.e("Stellar",
"fundsFromTrustedSource $coinData!!.fundsFromTrustedSource, " +
"fundsSentToTrustedSource $coinData!!.fundsSentToTrustedSource")
- blockchainRequestsCallbacks.onComplete(!ctx.hasError())
+ coinData!!.isBalanceReceived = true
}
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
CoroutineScope(Dispatchers.IO).launch { requestPayments(blockchainRequestsCallbacks) }
- val serverApi = ServerApiStellar(Blockchain.Stellar)
- val listener: ServerApiStellar.Listener = object : ServerApiStellar.Listener {
- override fun onSuccess(request: StellarRequest.Base) {
- Log.i(TAG, "onSuccess: " + request.javaClass.simpleName)
- if (request is StellarRequest.Balance) {
- coinData!!.accountResponse = request.accountResponse
- coinData!!.validationNodeDescription = serverApi.currentURL
-
- blockchainRequestsCallbacks.onProgress()
- } else if (request is Ledgers) {
- coinData!!.setLedgerResponse(request.ledgerResponse)
- if (serverApi.isRequestsSequenceCompleted) {
- } else {
- blockchainRequestsCallbacks.onProgress()
- }
- } else {
- ctx.error = "Invalid request logic"
- blockchainRequestsCallbacks.onComplete(false)
- }
- }
-
- override fun onFail(request: StellarRequest.Base) {
- Log.i(TAG, "onFail: " + request.javaClass.simpleName + " " + request.error)
- if (request.errorResponse != null && request.errorResponse.code == 404) {
- coinData!!.isError404 = true
- } else {
- ctx.error = request.error
- }
- if (serverApi.isRequestsSequenceCompleted) {
- if (ctx.hasError()) {
- blockchainRequestsCallbacks.onComplete(false)
- } else {
- blockchainRequestsCallbacks.onComplete(true)
- }
- } else {
- blockchainRequestsCallbacks.onProgress()
- }
- }
- }
- serverApi.setListener(listener)
- serverApi.requestData(ctx, StellarRequest.Balance(coinData!!.wallet))
- serverApi.requestData(ctx, Ledgers())
}
@Throws(Exception::class)
@@ -463,7 +415,7 @@ class XlmTagEngine : CoinEngine {
private val multiplier = BigDecimal("10000000")
- const val OPERATIONS_LIMIT = 50
+ const val OPERATIONS_LIMIT = 200
private val TRUSTED_DESTINATION = "GAYPZMHFZERB42ONEJ4CY6ADDVTINEXMY6OZ5G6CLR4HHVKOSNJSZGMM"
private val TRUSTED_SOURCE = "GAZY7H4BWWEVB6QGB4RV3LW7DH5NO5CD5O6JCEQXA7N2UCGZSAPJFYW2"
}
diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java
index 9ae763d5fa..73db5449a5 100644
--- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java
+++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java
@@ -228,11 +228,11 @@ public class XrpEngine extends CoinEngine {
}
}
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
- }
+// if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) {
+// balanceValidator.setScore(80);
+// balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
+// balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance);
+// }
return true;
} catch (Exception e) {
diff --git a/app/src/main/res/drawable/ic_add_a_photo.xml b/app/src/main/res/drawable/ic_add_a_photo.xml
new file mode 100644
index 0000000000..e076c77343
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_a_photo.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_flip_camera.xml b/app/src/main/res/drawable/ic_flip_camera.xml
new file mode 100644
index 0000000000..fcf92139db
--- /dev/null
+++ b/app/src/main/res/drawable/ic_flip_camera.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml
new file mode 100644
index 0000000000..e6c43fda6c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_person.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_take_picture.xml b/app/src/main/res/drawable/ic_take_picture.xml
new file mode 100644
index 0000000000..606310bdc7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_take_picture.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_camera.xml b/app/src/main/res/layout/fragment_camera.xml
new file mode 100644
index 0000000000..96170adca9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_camera.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_id.xml b/app/src/main/res/layout/fragment_id.xml
new file mode 100644
index 0000000000..ab10c02869
--- /dev/null
+++ b/app/src/main/res/layout/fragment_id.xml
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_issue_new_id.xml b/app/src/main/res/layout/fragment_issue_new_id.xml
new file mode 100644
index 0000000000..dbd0e752cb
--- /dev/null
+++ b/app/src/main/res/layout/fragment_issue_new_id.xml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_validate_id.xml b/app/src/main/res/layout/fragment_validate_id.xml
new file mode 100644
index 0000000000..2fcc5686b7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_validate_id.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_write_id.xml b/app/src/main/res/layout/fragment_write_id.xml
new file mode 100644
index 0000000000..beffa58146
--- /dev/null
+++ b/app/src/main/res/layout/fragment_write_id.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_id.xml b/app/src/main/res/layout/layout_id.xml
new file mode 100644
index 0000000000..8daafa88d6
--- /dev/null
+++ b/app/src/main/res/layout/layout_id.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_touch_card.xml b/app/src/main/res/layout/layout_touch_card.xml
index b46a4829c5..23321be35f 100644
--- a/app/src/main/res/layout/layout_touch_card.xml
+++ b/app/src/main/res/layout/layout_touch_card.xml
@@ -86,6 +86,7 @@
android:background="@color/white" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 70870483ed..2f46c3052b 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -85,7 +85,7 @@
Aucun porte-monnaie compatible installé
- Veuillez patienter pendant que la transaction précédente est confirmée dans la blockchain
+ Veuillez patienter que votre précédente transaction soit confirmée dans la blockchai
Impossible d\'obtenir toutes les entrées. Balayez vers le bas pour actualiser.
La carte n\'a plus de signature!
Si vous oubliez votre nouveau code PIN, vous perdrez votre argent pour toujours!
@@ -132,7 +132,7 @@
Solde confirmé dans la blockchain
Solde ne peut pas être vérifié. Balayez vers le bas pour actualiser..
- Attendez la confirmation dans la chaîne de blocs
+ En attente de confirmation dans la blockchain
Restaurez la connexion Internet pour obtenir un solde sécurisé de la chaîne de blocs
Impossible d\'obtenir le
solde de la blockchain. Restaurez la connexion Internet pour être plus confiant.
@@ -172,7 +172,7 @@
Veuillez patienter pendant l\'envoi du paiement…
- La transaction a été signée et envoyée avec succès au noeud de la blockchain. La balance de porte-monnaie sera mise à jour dans un moment
+ La transaction a été signée et envoyée avec succès au noeud de la blockchain. Le solde de votre porte-monnaie sera mise à jour dès que transaction sera validée sur la blockchain
Réessayez. Échec d\'envoi de la transaction (%s)
Impossible de signer la transaction. Veillez à entrer le code PIN2 correct!
Le montant et les frais sont supérieurs au solde total du porte-monnaie!
@@ -196,9 +196,9 @@
Normal
Priorité
Frais inclus
- Frais non compris
+ Frais non inclus
(frais inclus)
- (sans frais)
+ (frais non inclus)
Les données obtenues sont obsolètes! Réessayez
Impossible d\'atteindre le nœud de la blockchain active en cours. Réessayez
Impossible de vérifier le solde! Pas de connexion avec les noeuds de la blockchain
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 645ff3bfac..39cb7cd948 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -270,6 +270,12 @@
NOT GENUINE
ALREADY CLAIMED
+
+ VERIFIED
+ NOT REGISTERED
+ ID DEMO
+ Empty ID card
+
Save
Delete
diff --git a/build.gradle b/build.gradle
index c4edca4c38..dde59cbded 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.3.60'
+ ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()
diff --git a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
index 81dc1f368a..3fa43bfe6b 100644
--- a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
+++ b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
@@ -47,7 +47,7 @@ class LocalStorage
artworks = HashMap()
}
- if (artworks.count() < 31) {
+ if (artworks.count() < 32) {
// forceSave=true only on the last one
putResourceArtworkToCatalog(R.drawable.card_default, false)
putResourceArtworkToCatalog(R.drawable.card_default_nft, false)
@@ -78,6 +78,7 @@ class LocalStorage
putResourceArtworkToCatalog(R.drawable.card_ru043, false)
putResourceArtworkToCatalog(R.drawable.card_tg044, false)
putResourceArtworkToCatalog(R.drawable.card_tg046, false)
+ putResourceArtworkToCatalog(R.drawable.card_tgslix, false)
putResourceArtworkToCatalog(R.drawable.card_bc00, false)
putResourceArtworkToCatalog(R.drawable.card_ff32, true)
}
diff --git a/server-android/src/main/res/drawable/card_tgslix.png b/server-android/src/main/res/drawable/card_tgslix.png
new file mode 100644
index 0000000000..fe2f544598
Binary files /dev/null and b/server-android/src/main/res/drawable/card_tgslix.png differ
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/ProductMask.kt b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/ProductMask.kt
new file mode 100644
index 0000000000..65d8e4e44f
--- /dev/null
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/ProductMask.kt
@@ -0,0 +1,12 @@
+package com.tangem.tangem_card.data
+
+enum class ProductMask(val code: Byte) {
+ Note(0x01),
+ Tag(0x02),
+ Id(0x04);
+
+ companion object {
+ private val values = values()
+ fun byCode(code: Byte): ProductMask? = values.find { it.code == code }
+ }
+}
\ No newline at end of file
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java
index 8fe19575d6..37da532954 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java
@@ -1,9 +1,14 @@
package com.tangem.tangem_card.data;
import com.tangem.tangem_card.reader.CardProtocol;
+import com.tangem.tangem_card.reader.ProductMask;
import com.tangem.tangem_card.reader.SettingsMask;
+import com.tangem.tangem_card.reader.TLV;
+import com.tangem.tangem_card.reader.TLVList;
import com.tangem.tangem_card.util.Util;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
@@ -30,6 +35,7 @@ public class TangemCard {
private byte[] terminalPublicKey;
private boolean terminalIsLinked = false;
private byte[] tagSignature = null;
+ private byte[] idHash = null;
public String getBlockchainID() {
return blockchainID;
@@ -184,6 +190,14 @@ public class TangemCard {
this.terminalIsLinked = terminalIsLinked;
}
+ public byte[] getIdHash() {
+ return idHash;
+ }
+
+ public void setIdHash(byte[] idHash) {
+ this.idHash = idHash;
+ }
+
private int health = 0;
public int getHealth() {
@@ -288,6 +302,28 @@ public class TangemCard {
return "";
}
+ private byte[] issuerDataEx;
+ private byte[] issuerDataExSignature;
+ private int issuerDataExCounter;
+
+ public byte[] getIssuerDataEx() {
+ return issuerDataEx;
+ }
+
+ public byte[] getIssuerDataExSignature() {
+ return issuerDataExSignature;
+ }
+
+ public int getIssuerDataExCounter() {
+ return issuerDataExCounter;
+ }
+
+ public void setIssuerDataEx(byte[] value, byte[] signature, int counter) {
+ issuerDataEx = value;
+ issuerDataExSignature = signature;
+ issuerDataExCounter = counter;
+ }
+
private int pauseBeforePIN2 = 0;
public void setPauseBeforePIN2(int value) {
@@ -579,6 +615,101 @@ public class TangemCard {
this.UID = UID;
}
+ public int productMask = ProductMask.Note;
+
+ public void setProductMask(int productMask) {
+ this.productMask = productMask;
+ }
+
+ public boolean isIDCard() {
+ try {
+ return (productMask & ProductMask.IdCard) != 0;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public TLVList tlvIDCardData = null;
+
+ public void setTlvIdCardData(TLVList tlvIDCardData) {
+ this.tlvIDCardData = tlvIDCardData;
+ }
+
+ public void setTlvIDCardData(
+ String fullName,
+ String birthday,
+ String gender,
+ byte[] photo,
+ String issueDate,
+ String expireDate,
+ String trustedAddress) {
+
+ IDCardData data = new IDCardData();
+ data.fullName = fullName;
+ data.birthday = birthday;
+ data.gender = gender;
+ data.photo = photo;
+ data.issueDate = issueDate;
+ data.expireDate = expireDate;
+ data.trustedAddress = trustedAddress;
+
+ this.tlvIDCardData = data.toTLVList();
+ }
+
+ public boolean hasIDCardData() {
+ if (tlvIDCardData == null) {
+ if (issuerDataEx == null) return false;
+ try {
+ tlvIDCardData = TLVList.fromBytes(issuerDataEx);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ return tlvIDCardData.hasTag(TLV.Tag.TAG_FullName) && tlvIDCardData.hasTag(TLV.Tag.TAG_Birthday) && tlvIDCardData.hasTag(TLV.Tag.TAG_Gender) && tlvIDCardData.hasTag(TLV.Tag.TAG_Photo)
+ && tlvIDCardData.hasTag(TLV.Tag.TAG_IssueDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_ExpireDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_TrustedAddress);
+ }
+
+ public static class IDCardData {
+ public String fullName;
+ public String birthday;
+ public String gender;
+ public byte[] photo;
+ public String issueDate;
+ public String expireDate;
+ public String trustedAddress;
+
+ public TLVList toTLVList() {
+ TLVList tlvIDCardData = new TLVList();
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_FullName, fullName.getBytes(StandardCharsets.UTF_8)));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_Birthday, birthday.getBytes(StandardCharsets.UTF_8)));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_Gender, gender.getBytes(StandardCharsets.UTF_8)));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_Photo, photo));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_IssueDate, issueDate.getBytes(StandardCharsets.UTF_8)));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_ExpireDate, expireDate.getBytes(StandardCharsets.UTF_8)));
+ tlvIDCardData.add(new TLV(TLV.Tag.TAG_TrustedAddress, trustedAddress.getBytes(StandardCharsets.UTF_8)));
+ return tlvIDCardData;
+ }
+
+ public static IDCardData fromTLVList(TLVList tlvIDCardData) {
+ IDCardData result = new IDCardData();
+ result.fullName = tlvIDCardData.getTLV(TLV.Tag.TAG_FullName).getAsString();
+ result.birthday = tlvIDCardData.getTLV(TLV.Tag.TAG_Birthday).getAsString();
+ result.gender = tlvIDCardData.getTLV(TLV.Tag.TAG_Gender).getAsString();
+ result.photo = tlvIDCardData.getTLV(TLV.Tag.TAG_Photo).Value;
+ result.issueDate = tlvIDCardData.getTLV(TLV.Tag.TAG_IssueDate).getAsString();
+ result.expireDate = tlvIDCardData.getTLV(TLV.Tag.TAG_ExpireDate).getAsString();
+ result.trustedAddress = tlvIDCardData.getTLV(TLV.Tag.TAG_TrustedAddress).getAsString();
+ return result;
+ }
+ }
+
+ public IDCardData getIDCardData() {
+ if (!hasIDCardData()) return null;
+ return IDCardData.fromTLVList(tlvIDCardData);
+ }
private byte[] offlineBalance;
@@ -638,164 +769,6 @@ public class TangemCard {
this.tagSignature = tagSignature;
}
-// public Bundle getAsBundle() {
-// Bundle B = new Bundle();
-// saveToBundle(B);
-// return B;
-// }
-//
-// public void saveToBundle(Bundle B) {
-// try {
-// B.putString("UID", UID);
-// B.putByteArray("CID", CID);
-// B.putString("PIN", PIN);
-// B.putString("PIN2", PIN2.name());
-// B.putString("Status", status.name());
-// B.putString("Blockchain", blockchainID);
-// B.putString("BlockchainName", blockchainName);
-// B.putInt("TokensDecimal", tokensDecimal);
-// B.putString("TokenSymbol", tokenSymbol);
-// B.putString("ContractAddress", contractAddress);
-// if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime());
-// B.putInt("RemainingSignatures", remainingSignatures);
-// B.putInt("MaxSignatures", maxSignatures);
-// B.putInt("Health", health);
-// if (settingsMask != null) B.putInt("settingsMask", settingsMask);
-// B.putInt("pauseBeforePIN2", pauseBeforePIN2);
-// if (signingMethod != null) B.putString("signingMethod", signingMethod.name());
-// if (manufacturer != null) B.putString("Manufacturer", manufacturer.name());
-// if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name());
-// if (issuer != null) B.putString("Issuer", issuer.getID());
-// if (issuerPublicDataKey != null) B.putByteArray("IssuerPublicDataKey", issuerPublicDataKey);
-// if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion);
-// if (batch != null) B.putString("Batch", batch);
-// B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed);
-// B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid());
-// B.putByteArray("CardPublicKey", getCardPublicKey());
-//
-// B.putInt("SignedHashes", getSignedHashes());
-// B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid());
-// if (pbWalletKey != null)
-// B.putByteArray("PublicKey", pbWalletKey);
-// if (pbWalletKeyRar != null)
-// B.putByteArray("PublicKeyRar", pbWalletKeyRar);
-//
-// if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance());
-//
-// if (getDenomination() != null) B.putByteArray("Denomination", getDenomination());
-// if (getDenominationText() != null) B.putString("DenominationText", getDenominationText());
-//
-// if (getIssuerData() != null && getIssuerDataSignature() != null) {
-// B.putByteArray("IssuerData", getIssuerData());
-// B.putByteArray("IssuerDataSignature", getIssuerDataSignature());
-// B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData());
-// }
-//
-// if (codeConfirmed != null)
-// B.putBoolean("codeConfirmed", codeConfirmed);
-//
-// if (codeConfirmed != null)
-// B.putBoolean("codeConfirmed", codeConfirmed);
-//
-// if (onlineVerified != null)
-// B.putBoolean("onlineVerified", onlineVerified);
-//
-// if (onlineValidated != null)
-// B.putBoolean("onlineValidated", onlineValidated);
-//
-// if (codeConfirmed != null)
-// B.putBoolean("codeConfirmed", codeConfirmed);
-//
-// if (codeConfirmed != null)
-// B.putBoolean("codeConfirmed", codeConfirmed);
-//
-// if (onlineVerified != null)
-// B.putBoolean("onlineVerified", onlineVerified);
-//
-// if (onlineValidated != null)
-// B.putBoolean("onlineValidated", onlineValidated);
-// } catch (Exception e) {
-// Log.e("Can't save to bundle ", e.getMessage());
-// }
-//
-// }
-//
-// public void loadFromBundle(Bundle B) {
-// UID = B.getString("UID");
-// CID = B.getByteArray("CID");
-// PIN = B.getString("PIN");
-// PIN2 = PIN2_Mode.valueOf(B.getString("PIN2"));
-// status = Status.valueOf(B.getString("Status"));
-// blockchainID = B.getString("Blockchain");
-// tokensDecimal = B.getInt("TokensDecimal", 18);
-// tokenSymbol = B.getString("TokenSymbol", "");
-// contractAddress = B.getString("ContractAddress", "");
-// if (B.containsKey("BlockchainName"))
-// blockchainName = B.getString("BlockchainName", "");
-// if (B.containsKey("dtPersonalization")) {
-// dtPersonalization = new Date(B.getLong("dtPersonalization"));
-// }
-// remainingSignatures = B.getInt("RemainingSignatures");
-// maxSignatures = B.getInt("MaxSignatures");
-// health = B.getInt("health");
-// if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask");
-// pauseBeforePIN2 = B.getInt("pauseBeforePIN2");
-// if (B.containsKey("signingMethod"))
-// signingMethod = SigningMethod.valueOf(B.getString("signingMethod"));
-// if (B.containsKey("Manufacturer"))
-// manufacturer = Manufacturer.valueOf(B.getString("Manufacturer"));
-// manufacturerConfirmed = B.getBoolean("ManufacturerConfirmed");
-// if (B.containsKey("EncryptionMode"))
-// encryptionMode = EncryptionMode.valueOf(B.getString("EncryptionMode"));
-// else
-// encryptionMode = null;
-//
-// if (B.containsKey("SignedHashes")) setSignedHashes(B.getInt("SignedHashes"));
-//
-// if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer"));
-// if (B.containsKey("IssuerPublicDataKey"))
-// issuerPublicDataKey = B.getByteArray("IssuerPublicDataKey");
-//
-// if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion");
-// if (B.containsKey("Batch")) batch = B.getString("Batch");
-//
-// cardPublicKeyValid = B.getBoolean("CardPublicKeyValid");
-// if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey"));
-//
-// if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance"));
-// else clearOfflineBalance();
-//
-// if (B.containsKey("Denomination") && B.containsKey("DenominationText")) {
-// setDenomination(B.getByteArray("Denomination"), B.getString("DenominationText"));
-// } else if (B.containsKey("Denomination")) {
-// setDenomination(B.getByteArray("Denomination"), "N/A");
-// } else clearDenomination();
-//
-// if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature"))
-// setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"));
-// else setIssuerData(null, null);
-//
-// if (B.containsKey("NeedWriteIssuerData"))
-// setNeedWriteIssuerData(B.getBoolean("NeedWriteIssuerData"));
-//
-// walletPublicKeyValid = B.getBoolean("WalletPublicKeyValid");
-// if (B.containsKey("PublicKey")) {
-// pbWalletKey = B.getByteArray("PublicKey");
-// }
-// if (B.containsKey("PublicKeyRar")) {
-// pbWalletKeyRar = B.getByteArray("PublicKeyRar");
-// }
-//
-// if (B.containsKey("codeConfirmed"))
-// codeConfirmed = B.getBoolean("codeConfirmed");
-//
-// if (B.containsKey("onlineVerified"))
-// onlineVerified = B.getBoolean("onlineVerified");
-//
-// if (B.containsKey("onlineValidated"))
-// onlineValidated = B.getBoolean("onlineValidated");
-// }
-
public enum EncryptionMode {
None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2);
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java
index 94e1a7b799..6e0f145558 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/CardProtocol.java
@@ -1,5 +1,6 @@
package com.tangem.tangem_card.reader;
+import com.tangem.tangem_card.data.Issuer;
import com.tangem.tangem_card.data.Manufacturer;
import com.tangem.tangem_card.data.TangemCard;
import com.tangem.tangem_card.util.Log;
@@ -17,6 +18,7 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
+import java.util.Arrays;
import javax.crypto.KeyAgreement;
@@ -99,6 +101,11 @@ public class CardProtocol {
private NfcReader mIsoDep;
+ public NfcReader getReader()
+ {
+ return mIsoDep;
+ }
+
public static final String DefaultPIN = "000000";
public static final String DefaultPIN2 = "000";
@@ -957,9 +964,11 @@ public class CardProtocol {
}
private void prepareDataForLinkingTerminal(CommandApdu apdu, byte[] data) throws Exception {
- byte[] transactionSignature = CardCrypto.Signature(mCard.getTerminalPrivateKey(), data);
- apdu.addTLV(TLV.Tag.TAG_Terminal_TransactionSignature, transactionSignature);
- addTerminalPublicKeyToApdu(apdu);
+ if (mCard.getTerminalPrivateKey() != null) {
+ byte[] transactionSignature = CardCrypto.Signature(mCard.getTerminalPrivateKey(), data);
+ apdu.addTLV(TLV.Tag.TAG_Terminal_TransactionSignature, transactionSignature);
+ addTerminalPublicKeyToApdu(apdu);
+ }
}
/**
@@ -1095,6 +1104,12 @@ public class CardProtocol {
if (issuerData == null || issuerDataSignature == null)
throw new TangemException("Invalid answer format (GetIssuerData)");
+ if( issuerData.Value.length==0 && issuerDataSignature.Value.length==0 )
+ {
+ mCard.setIssuerData(null, null);
+ return new TLVList();
+ }
+
ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
bsDataToVerify.write(mCard.getCID());
bsDataToVerify.write(issuerData.Value);
@@ -1117,6 +1132,185 @@ public class CardProtocol {
}
}
+ /**
+ * Series of GET_ISSUER_DATA command to read extra data and verify issuer signature of returned extra data
+ * See [1] 3.3, 8.7
+ * This command returns Issuer_Extra_Data data block and its issuer’s signature.
+ *
+ * @return TLVList with issuerData (if success read and verify)
+ * @throws Exception - if something went wrong
+ */
+ public TLVList run_GetIssuerDataEx(Notifications notifications) throws Exception {
+ boolean needReadIssuerDataCounter = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
+ TLV issuerDataSignature = null;
+ TLV issuerDataCounter = null;
+ TLV issuerDataSize = null;
+ ByteArrayOutputStream bsIssuerDataEx = new ByteArrayOutputStream();
+ do {
+
+ CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData);
+ rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01);
+ rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, bsIssuerDataEx.size());
+
+ Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
+
+ ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
+
+ if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
+ Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
+ TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data);
+ if (issuerData != null && issuerData.Value != null)
+ bsIssuerDataEx.write(issuerData.Value);
+ issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature);
+ issuerDataCounter = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Counter);
+ if (issuerDataSize == null)
+ issuerDataSize = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Size);
+ } else {
+ throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
+ }
+ if (issuerDataSize != null && issuerDataSize.getAsInt() != 0) {
+ if (notifications != null)
+ notifications.onReadProgress(this, (int) Math.round(bsIssuerDataEx.size() * 100.0 / issuerDataSize.getAsInt()));
+ Log.i(logTag, String.format("Read %d/%d bytes", bsIssuerDataEx.size(), issuerDataSize.getAsInt()));
+ }
+
+ } while ((issuerDataSignature == null) || (needReadIssuerDataCounter && (issuerDataCounter == null)));
+ boolean protectIssuerDataAgainstReplay = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
+
+ if (notifications != null) notifications.onReadProgress(this, 100);
+
+ if (bsIssuerDataEx.size() > 0 && issuerDataSignature != null && issuerDataSignature.Value != null) {
+ ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
+ bsDataToVerify.write(mCard.getCID());
+ bsDataToVerify.write(bsIssuerDataEx.toByteArray());
+ if (protectIssuerDataAgainstReplay) {
+ bsDataToVerify.write(issuerDataCounter.Value);
+ }
+ try {
+ if (CardCrypto.VerifySignature(mCard.getIssuerPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) {
+ mCard.setIssuerDataEx(bsIssuerDataEx.toByteArray(), issuerDataSignature.Value, issuerDataCounter.getAsInt());
+ return TLVList.fromBytes(bsIssuerDataEx.toByteArray());
+ } else {
+ throw new TangemException("Invalid issuer data read (signature verification failed)");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new TangemException("Invalid issuer data read");
+ }
+ } else {
+ mCard.setIssuerDataEx(null, null, issuerDataCounter.getAsInt());
+ return null;
+ }
+ }
+
+ /**
+ * Series of WRITE_ISSUER_DATA command to write extra data:
+ * 1. start write procedure
+ * 2. series of writing data blocks
+ * 3. finalize write procedure
+ * This command make signatures with provided issuer privateDataKey
+ * - to start write procedure (sign CID|Counter|Size)
+ * - to finalize write procedure (sign CID|dataEx|Counter)
+ * See [1] 3.3, 8.8
+ *
+ * @throws Exception - if something went wrong
+ */
+ public void run_WriteIssuerDataEx(Notifications notifications, byte[] dataEx, byte[] issuerPrivateDataKey) throws Exception {
+ boolean needCounter = readResult.getTLV(TLV.Tag.TAG_SettingsMask) != null && (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
+
+ Log.i(logTag, "WriteIssuerDataEx - Start");
+
+ int issuerDataCounter = 0;
+ ByteArrayOutputStream osDataToSign = new ByteArrayOutputStream();
+ osDataToSign.write(mCard.getCID());
+ if (needCounter) {
+ issuerDataCounter = mCard.getIssuerDataExCounter() + 1;
+ osDataToSign.write(Util.intToByteArray4(issuerDataCounter));
+ }
+ osDataToSign.write(Util.intToByteArray2(dataEx.length));
+
+ byte[] issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray());
+
+ CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
+ rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01);
+ if (needCounter) {
+ rqApdu.addTLV_U32(TLV.Tag.TAG_Issuer_Data_Counter, issuerDataCounter);
+ }
+ rqApdu.addTLV_U16(TLV.Tag.TAG_Size, dataEx.length);
+ rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
+
+ Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
+
+ ResponseApdu rspApdu = SendAndReceive(rqApdu, true);
+
+ if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
+ Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
+ } else {
+ throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
+ }
+
+ if (notifications != null)
+ notifications.onReadProgress(this, 10);
+
+
+ Log.i(logTag, "WriteIssuerDataEx - Send data");
+ int offset = 0;
+ int partSize = 1524;
+ while (offset < dataEx.length) {
+
+ if (dataEx.length - offset < partSize) {
+ partSize = dataEx.length - offset;
+ }
+ byte[] part = Arrays.copyOfRange(dataEx, offset, offset + partSize);
+
+ rqApdu = StartPrepareCommand(INS.WriteIssuerData);
+ rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x02);
+ rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, offset);
+ rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, part);
+
+ Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
+
+ rspApdu = SendAndReceive(rqApdu, true);
+
+ if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
+ Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
+ } else {
+ throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
+ }
+
+ offset += partSize;
+ if (notifications != null)
+ notifications.onReadProgress(this, 10+(int) Math.round(offset * 80.0 / dataEx.length));
+ }
+
+ Log.i(logTag, "WriteIssuerDataEx - Finalize");
+
+ osDataToSign = new ByteArrayOutputStream();
+ osDataToSign.write(mCard.getCID());
+ osDataToSign.write(dataEx);
+ if (needCounter) {
+ osDataToSign.write(Util.intToByteArray4(issuerDataCounter));
+ }
+ issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray());
+
+ rqApdu = StartPrepareCommand(INS.WriteIssuerData);
+ rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x03);
+ rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
+
+ Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
+
+ rspApdu = SendAndReceive(rqApdu, true);
+
+ if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
+ Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
+ } else {
+ throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
+ }
+ if (notifications != null)
+ notifications.onReadProgress(this, 100);
+ }
+
+
/**
* Execute consecutive READ commands with increasing encryption level from EncryptionMode.None to EncryptionMode.Strong, see {@link TangemCard.EncryptionMode}
* If card requires stricter encryption level it returns SW.NEED_ENCRYPTION status word
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java
new file mode 100644
index 0000000000..15516edaab
--- /dev/null
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/ProductMask.java
@@ -0,0 +1,25 @@
+package com.tangem.tangem_card.reader;
+
+/**
+ * Created by dvol on 15.07.2019.
+ */
+
+public class ProductMask {
+ public static final int Note = 0x0001;
+ public static final int Tag = 0x0002;
+ public static final int IdCard = 0x0004;
+
+ public static String getDescription(int iValue) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ if ((iValue & ProductMask.Note) != 0) sb.append("Note, ");
+ if ((iValue & ProductMask.Tag) != 0)
+ sb.append("Tag, ");
+ if ((iValue & ProductMask.IdCard) != 0)
+ sb.append("IdCard, ");
+
+ if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java
index 81470d9586..0513936d3d 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/SettingsMask.java
@@ -29,8 +29,16 @@ public class SettingsMask {
public static final int DisablePrecomputedNDEF = 0x00010000;
+ public static final int SkipSecurityDelayIfValidatedByIssuer = 0x00020000;
+ public static final int SkipCheckPIN2andCVCIfValidatedByIssuer = 0x00040000;
public static final int SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000;
+ public static final int RestrictOverwriteIssuerDataEx = 0x00100000;
+
+ public static final int RequireTermTxSignature = 0x01000000;
+ public static final int RequireTermCertSignature = 0x02000000;
+ public static final int CheckPIN3onCard = 0x04000000;
+
public static String getDescription(int iValue) {
StringBuilder sb=new StringBuilder();
sb.append("[");
@@ -63,6 +71,24 @@ public class SettingsMask {
if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0)
sb.append("SkipSecurityDelayIfValidatedByLinkedTerminal, ");
+
+ if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByIssuer) != 0)
+ sb.append("SkipSecurityDelayIfValidatedByIssuer, ");
+ if ((iValue & SettingsMask.SkipCheckPIN2andCVCIfValidatedByIssuer) != 0)
+ sb.append("SkipCheckPIN2andCVCIfValidatedByIssuer, ");
+
+ if ((iValue & SettingsMask.RestrictOverwriteIssuerDataEx) != 0)
+ sb.append("RestrictOverwriteIssuerDataEx, ");
+
+ if ((iValue & SettingsMask.RequireTermTxSignature) != 0)
+ sb.append("RequireTermTxSignature, ");
+
+ if ((iValue & SettingsMask.RequireTermCertSignature) != 0)
+ sb.append("RequireTermCertSignature, ");
+
+ if ((iValue & SettingsMask.CheckPIN3onCard) != 0)
+ sb.append("CheckPIN3onCard, ");
+
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
sb.append("]");
return sb.toString();
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java
index 4188e0ca54..4e704fe1f0 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLV.java
@@ -44,15 +44,27 @@ public class TLV {
TAG_Session_Key_B(0x1B),
TAG_Pause(0x1C),
+ TAG_PIN3(0x1D),
+ TAG_NewPIN3(0x1E),
+ TAG_CrEx_Key(0x1F),
+
TAG_Manufacture_ID(0x20),
TAG_Manufacturer_Signature(0x21),
+ TAG_Mode(0x23),
+ TAG_Offset(0x24),
+ TAG_Size(0x25),
+
+ TAG_User_Data(0x2A),
+ TAG_User_ProtectedData(0x2B),
+
TAG_Issuer_Data_PublicKey(0x30),
TAG_Issuer_Transaction_PublicKey(0x31),
TAG_Issuer_Data(0x32),
TAG_Issuer_Data_Signature(0x33),
TAG_Issuer_Transaction_Signature(0x34),
TAG_Issuer_Data_Counter(0x35),
+ TAG_Acquirer_PublicKey(0x37),
TAG_IsActivated(0x3A),
TAG_ActivationSeed(0x3B),
@@ -65,12 +77,38 @@ public class TLV {
TAG_TrOut_Hash(0x50),
TAG_TrOut_HashSize(0x51),
TAG_TrOut_Raw(0x52),
+ TAG_TrOut_Amount(0x53),
+
+ TAG_Terminal_PaymentFlowVersion(0x54),
+ TAG_Terminal_Certificate(0x55),
+ // hash of extra data to transaction (for SIGN_HASH_EXTERNAL)
+ TAG_TrOut_Extra_Hash(0x56),
+ TAG_Terminal_Transaction_Signature(0x57),
+ TAG_Terminal_Certificate_Id(0x5A),
+ TAG_Terminal_Certificate_Param(0x5B),
+ TAG_Terminal_Certificate_PublicKey(0x5C),
+ TAG_Terminal_Certificate_Signature(0x5D),
TAG_Wallet_PublicKey(0x60),
TAG_Signature(0x61),
TAG_RemainingSignatures(0x62),
TAG_SignedHashes(0x63),
+ TAG_CheckWalletCounter(0x64),
+
+ TAG_CrEx_Salt(0x65),
+ TAG_CrEx_Message(0x66),
+ TAG_CrEx_CryptedMessage(0x67),
+ TAG_CrEx_HMAC(0x68),
+
+ TAG_CrEx_RequirePIN3(0x69),
+ TAG_TrOut_Extra_Signature(0x6A),
+ TAG_SignCommand_ChecksPassed(0x6B),
+
+ TAG_Wallet_PrivateKey(0x70),
+ TAG_Card_PrivateKey(0x71),
+ TAG_Block_Reason(0x72),
+
TAG_Firmware(0x80),
TAG_Batch(0x81),
TAG_ManufactureDateTime(0x82),
@@ -79,6 +117,10 @@ public class TLV {
TAG_Manufacturer_PublicKey(0x85),
TAG_CardID_Manufacturer_Signature(0x86),
+ TAG_ProductMask(0x8A),
+
+ TAG_PinLessFloorLimit(0x90),
+
TAG_Token_Symbol(0xA0),
TAG_Token_Contract_Address(0xA1),
TAG_Token_Decimal(0xA2),
@@ -87,6 +129,14 @@ public class TLV {
TAG_LastSign_Date(0xC2),
TAG_DenominationText(0xC3),
+ TAG_FullName (0xD0),
+ TAG_Birthday (0xD1),
+ TAG_Photo (0xD2),
+ TAG_Gender(0xD3),
+ TAG_IssueDate (0xD4),
+ TAG_ExpireDate(0xD5),
+ TAG_TrustedAddress (0xD6),
+
TAG_Terminal_IsLinked(0x58),
TAG_Terminal_PublicKey(0x5C),
TAG_Terminal_TransactionSignature(0x57);
@@ -172,7 +222,7 @@ public class TLV {
}
public String getAsString() {
- if( Value.length==0 ) return "";
+ if (Value.length == 0) return "";
if (Value[Value.length - 1] == 0) {
String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8"));
@@ -214,18 +264,29 @@ public class TLV {
return String.format("%s[]: [[NULL]]", tag.name());
}
case TAG_SettingsMask: {
- StringBuilder sb=new StringBuilder();
- if( Value!=null ) {
+ StringBuilder sb = new StringBuilder();
+ if (Value != null) {
try {
int iValue = Util.byteArrayToInt(Value);
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), SettingsMask.getDescription(iValue));
- }
- catch (Exception e)
- {
+ } catch (Exception e) {
e.printStackTrace();
return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value));
}
- }else{
+ } else {
+ return String.format("%s[]: [[NULL]]", tag.name());
+ }
+ }
+ case TAG_ProductMask: {
+ if (Value != null) {
+ try {
+ int iValue = Util.byteArrayToInt(Value);
+ return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), ProductMask.getDescription(iValue));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value));
+ }
+ } else {
return String.format("%s[]: [[NULL]]", tag.name());
}
}
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java
index 40c36a777b..ebc3123d28 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/reader/TLVList.java
@@ -9,10 +9,11 @@ import com.tangem.tangem_card.util.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
-public class TLVList extends ArrayList {
+public class TLVList extends ArrayList implements Serializable {
public String getParsedTLVs(String Prefix) {
String parsed = "";
for (int i = 0; i < size(); i++) {
@@ -85,4 +86,8 @@ public class TLVList extends ArrayList {
while (tlv != null);
return tlvList;
}
+
+ public boolean hasTag(TLV.Tag tag) {
+ return getTLV(tag)!=null;
+ }
}
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java
index 453ecd2b28..d0f5274e93 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/CustomReadCardTask.java
@@ -7,6 +7,7 @@ import com.tangem.tangem_card.data.external.PINsProvider;
import com.tangem.tangem_card.reader.CardCrypto;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.reader.NfcReader;
+import com.tangem.tangem_card.reader.ProductMask;
import com.tangem.tangem_card.reader.TLV;
import com.tangem.tangem_card.reader.TLVException;
import com.tangem.tangem_card.reader.TLVList;
@@ -137,6 +138,12 @@ public class CustomReadCardTask extends Thread {
mCard.setBatch(tlvCardData.getTLV(TLV.Tag.TAG_Batch).getAsHexString());
+ if( tlvCardData.hasTag(TLV.Tag.TAG_ProductMask)) {
+ mCard.setProductMask(tlvCardData.getTagAsInt(TLV.Tag.TAG_ProductMask));
+ }else{
+ mCard.setProductMask(ProductMask.Note);
+ }
+
TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol);
TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address);
TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal);
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java
index 57cb73c822..4b70353a3b 100644
--- a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/VerifyCardTask.java
@@ -46,6 +46,10 @@ public class VerifyCardTask extends CustomReadCardTask {
}
mNotifications.onReadProgress(protocol, 90);
+ if( protocol.getCard().isIDCard() )
+ {
+ protocol.run_GetIssuerDataEx(mNotifications);
+ }
}
}
\ No newline at end of file
diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java
new file mode 100644
index 0000000000..9329607641
--- /dev/null
+++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/tasks/WriteIDCardTask.java
@@ -0,0 +1,36 @@
+package com.tangem.tangem_card.tasks;
+
+import com.tangem.tangem_card.data.TangemCard;
+import com.tangem.tangem_card.data.external.CardDataSubstitutionProvider;
+import com.tangem.tangem_card.data.external.PINsProvider;
+import com.tangem.tangem_card.reader.CardProtocol;
+import com.tangem.tangem_card.reader.NfcReader;
+
+/**
+ * Created by dvol on 19.01.2020.
+ */
+
+public class WriteIDCardTask extends CustomReadCardTask {
+ public static final String TAG = WriteIDCardTask.class.getSimpleName();
+
+ private TangemCard.IDCardData idCardData;
+ private byte[] issuerPrivateDataKey;
+ public WriteIDCardTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, TangemCard.IDCardData idCardData, byte[] issuerPrivateDataKey, CardProtocol.Notifications notifications) {
+ super(card, reader, cardDataSubstitutionProvider, pinsProvider, notifications);
+ this.idCardData=idCardData;
+ this.issuerPrivateDataKey=issuerPrivateDataKey;
+ }
+
+ @Override
+ public void run_Task() throws Exception {
+ mNotifications.onReadProgress(protocol, 0);
+ if (isCancelled) return;
+
+ if( !protocol.getCard().isIDCard() )
+ throw new Exception("It's not an IDCard!");
+
+ protocol.run_WriteIssuerDataEx(mNotifications,idCardData.toTLVList().toBytes(),issuerPrivateDataKey);
+ mNotifications.onReadProgress(protocol, 100);
+ }
+
+}
\ No newline at end of file
diff --git a/tangem-core/src/main/java/com/tangem/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/CardEnvironment.kt
index 8c32ac506f..39e077006e 100644
--- a/tangem-core/src/main/java/com/tangem/CardEnvironment.kt
+++ b/tangem-core/src/main/java/com/tangem/CardEnvironment.kt
@@ -10,7 +10,8 @@ data class CardEnvironment(
val pin2: String = DEFAULT_PIN2,
val cardId: String? = null,
val terminalKeys: KeyPair? = null,
- val encryptionKey: ByteArray? = null
+ val encryptionKey: ByteArray? = null,
+ val cvc: ByteArray? = null
) {
companion object {
diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt
index 197c82891a..e381f57e3d 100644
--- a/tangem-core/src/main/java/com/tangem/CardManager.kt
+++ b/tangem-core/src/main/java/com/tangem/CardManager.kt
@@ -122,6 +122,37 @@ class CardManager(
runTask(task, cardId, callback)
}
+ /**
+ * This command will create a new wallet on the card having ‘Empty’ state.
+ * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
+ * App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand]
+ * and then transform it into an address of corresponding blockchain wallet
+ * according to a specific blockchain algorithm.
+ * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
+ * RemainingSignature is set to MaxSignatures.
+ * @param cardId CID, Unique Tangem card ID number.
+ */
+ fun createWallet(cardId: String,
+ callback: (result: TaskEvent) -> Unit) {
+ val createWalletCommand = CreateWalletCommand(cardId)
+ val task = SingleCommandTask(createWalletCommand)
+ runTask(task, cardId, callback)
+ }
+
+ /**
+ * This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
+
+ * If Is_Reusable flag is disabled, the card switches to ‘Purged’ state.
+ * ‘Purged’ state is final, it makes the card useless.
+ * @param cardId CID, Unique Tangem card ID number.
+ */
+ fun purgeWallet(cardId: String,
+ callback: (result: TaskEvent) -> Unit) {
+ val purgeWalletCommand = PurgeWalletCommand(cardId)
+ val task = SingleCommandTask(purgeWalletCommand)
+ runTask(task, cardId, callback)
+ }
+
/**
*/
@@ -157,6 +188,6 @@ class CardManager(
}
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
- return cardEnvironmentRepository[cardId] ?: CardEnvironment()
+ return cardEnvironmentRepository[cardId] ?: CardEnvironment(cardId = cardId)
}
}
\ No newline at end of file
diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt
new file mode 100644
index 0000000000..833795cbbf
--- /dev/null
+++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt
@@ -0,0 +1,71 @@
+package com.tangem.commands
+
+import com.tangem.CardEnvironment
+import com.tangem.common.apdu.CommandApdu
+import com.tangem.common.apdu.Instruction
+import com.tangem.common.apdu.ResponseApdu
+import com.tangem.common.extensions.calculateSha256
+import com.tangem.common.extensions.hexToBytes
+import com.tangem.common.tlv.Tlv
+import com.tangem.common.tlv.TlvMapper
+import com.tangem.common.tlv.TlvTag
+import com.tangem.tasks.TaskError
+
+class CreateWalletResponse(
+ /**
+ * CID, Unique Tangem card ID number.
+ */
+ val cardId: String,
+ /**
+ * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
+ */
+ val status: CardStatus,
+ /**
+
+ */
+ val walletPublicKey: ByteArray
+) : CommandResponse
+
+/**
+ * This command will create a new wallet on the card having ‘Empty’ state.
+ * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
+ * App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand]
+ * and then transform it into an address of corresponding blockchain wallet
+ * according to a specific blockchain algorithm.
+ * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
+ * RemainingSignature is set to MaxSignatures.
+ *
+ * @property cardId CID, Unique Tangem card ID number.
+ */
+class CreateWalletCommand(
+ private val cardId: String
+) : CommandSerializer() {
+
+ override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
+ val tlvData = mutableListOf(
+ Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
+ Tlv(TlvTag.CardId, cardId.hexToBytes()),
+ Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
+ )
+ if (cardEnvironment.cvc != null) {
+ tlvData.add(Tlv(TlvTag.Cvc, cardEnvironment.cvc))
+ }
+
+ return CommandApdu(Instruction.CreateWallet, tlvData)
+ }
+
+ override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? {
+ val tlvData = responseApdu.getTlvData() ?: return null
+
+ return try {
+ val mapper = TlvMapper(tlvData)
+ CreateWalletResponse(
+ cardId = mapper.map(TlvTag.CardId),
+ status = mapper.map(TlvTag.Status),
+ walletPublicKey = mapper.map(TlvTag.WalletPublicKey)
+ )
+ } catch (exception: Exception) {
+ throw TaskError.SerializeCommandError()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt
new file mode 100644
index 0000000000..f8c3feaf7f
--- /dev/null
+++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt
@@ -0,0 +1,57 @@
+package com.tangem.commands
+
+import com.tangem.CardEnvironment
+import com.tangem.common.apdu.CommandApdu
+import com.tangem.common.apdu.Instruction
+import com.tangem.common.apdu.ResponseApdu
+import com.tangem.common.extensions.calculateSha256
+import com.tangem.common.extensions.hexToBytes
+import com.tangem.common.tlv.Tlv
+import com.tangem.common.tlv.TlvMapper
+import com.tangem.common.tlv.TlvTag
+import com.tangem.tasks.TaskError
+
+class PurgeWalletResponse(
+ /**
+ * CID, Unique Tangem card ID number.
+ */
+ val cardId: String,
+ /**
+ * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
+ */
+ val status: CardStatus
+) : CommandResponse
+
+/**
+* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
+
+ * If Is_Reusable flag is disabled, the card switches to ‘Purged’ state.
+ * ‘Purged’ state is final, it makes the card useless.
+ * @property cardId CID, Unique Tangem card ID number.
+ */
+class PurgeWalletCommand(
+ private val cardId: String
+) : CommandSerializer() {
+
+ override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
+ val tlvData = mutableListOf(
+ Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
+ Tlv(TlvTag.CardId, cardId.hexToBytes()),
+ Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
+ )
+ return CommandApdu(Instruction.PurgeWallet, tlvData)
+ }
+
+ override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? {
+ val tlvData = responseApdu.getTlvData() ?: return null
+
+ return try {
+ val mapper = TlvMapper(tlvData)
+ PurgeWalletResponse(
+ cardId = mapper.map(TlvTag.CardId),
+ status = mapper.map(TlvTag.Status))
+ } catch (exception: Exception) {
+ throw TaskError.SerializeCommandError()
+ }
+ }
+}
\ No newline at end of file
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 491509ae08..ca292d37f4 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
@@ -52,7 +52,7 @@ enum class TlvTag(val code: Int) {
Pause(0x1C),
ManufactureId(0x20),
- ManufacturerSignature(0x21),
+ ManufacturerSignature(0x86),
IssuerDataPublicKey(0x30),
IssuerTransactionPublicKey(0x31),
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt
index 018ca60ce5..52b2951309 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt
@@ -45,6 +45,8 @@ class MainActivity : AppCompatActivity() {
tv_card_cid?.text = cardId
btn_sign.isEnabled = true
btn_read_issuer_data.isEnabled = true
+ btn_purge_wallet.isEnabled = true
+ btn_create_wallet.isEnabled = true
}
}
}
@@ -103,6 +105,32 @@ class MainActivity : AppCompatActivity() {
}
}
}
+ btn_purge_wallet?.setOnClickListener { _ ->
+ cardManager.purgeWallet(
+ cardId) {
+ when (it) {
+ is TaskEvent.Completion -> {
+ if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
+ }
+ is TaskEvent.Event -> runOnUiThread {
+ tv_card_cid?.text = it.data.status.name
+ }
+ }
+ }
+ }
+ btn_create_wallet?.setOnClickListener { _ ->
+ cardManager.createWallet(
+ cardId) {
+ when (it) {
+ is TaskEvent.Completion -> {
+ if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
+ }
+ is TaskEvent.Event -> runOnUiThread {
+ tv_card_cid?.text = it.data.status.name
+ }
+ }
+ }
+ }
}
private fun createSampleHashes(): Array {
diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml
index a1cecca79b..5759633c21 100644
--- a/tangem-demo/src/main/res/layout/activity_main.xml
+++ b/tangem-demo/src/main/res/layout/activity_main.xml
@@ -9,12 +9,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt
index 9c6356dd4e..669097245f 100644
--- a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt
+++ b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/data/TangemCardExtensions.kt
@@ -3,6 +3,7 @@ package com.tangem.tangem_sdk.data
import android.os.Bundle
import com.tangem.tangem_card.data.Manufacturer
import com.tangem.tangem_card.data.TangemCard
+import com.tangem.tangem_card.reader.TLVList
import com.tangem.tangem_card.util.Log
import java.util.*
@@ -61,6 +62,13 @@ fun TangemCard.loadFromBundle(B: Bundle) {
setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"))
else setIssuerData(null, null)
+ if (B.containsKey("IssuerDataEx") && B.containsKey("IssuerDataExSignature") && B.containsKey("IssuerDataExCounter"))
+ setIssuerDataEx(B.getByteArray("IssuerDataEx"), B.getByteArray("IssuerDataExSignature"), B.getInt("IssuerDataExCounter"))
+ else setIssuerDataEx(null, null, 0)
+
+ if( B.containsKey("ProductMask") )
+ setProductMask(B.getInt("ProductMask"))
+
if (B.containsKey("NeedWriteIssuerData"))
needWriteIssuerData = B.getBoolean("NeedWriteIssuerData")
@@ -86,6 +94,8 @@ fun TangemCard.loadFromBundle(B: Bundle) {
terminalIsLinked = B.getBoolean("terminalIsLinked")
if (B.containsKey("tagSignature")) tagSignature = B.getByteArray("tagSignature")
+ if (B.containsKey("idHash")) idHash = B.getByteArray("idHash")
+ if (B.containsKey("tlvIDCardData")) tlvIDCardData = B.getSerializable("tlvIDCardData") as TLVList
}
@@ -149,6 +159,14 @@ fun TangemCard.saveToBundle(B: Bundle) {
B.putBoolean("NeedWriteIssuerData", needWriteIssuerData)
}
+ if (issuerDataEx != null && issuerDataExSignature != null) {
+ B.putByteArray("IssuerDataEx", issuerDataEx)
+ B.putByteArray("IssuerDataExSignature", issuerDataExSignature)
+ B.putInt("IssuerDataExCounter", issuerDataExCounter)
+ }
+
+ B.putInt("ProductMask",productMask)
+
if (isCodeConfirmed != null)
B.putBoolean("codeConfirmed", isCodeConfirmed)
@@ -166,6 +184,9 @@ fun TangemCard.saveToBundle(B: Bundle) {
if (tagSignature != null) B.putByteArray("tagSignature", tagSignature)
+ if (idHash != null) B.putByteArray("idHash", idHash)
+ if (tlvIDCardData != null) B.putSerializable("tlvIDCardData", tlvIDCardData)
+
} catch (e: Exception) {
Log.e("Can't save to bundle ", e.message)
}
diff --git a/tangem-sdk-old/src/main/res/drawable/id_card_default.xml b/tangem-sdk-old/src/main/res/drawable/id_card_default.xml
new file mode 100644
index 0000000000..68210cf320
--- /dev/null
+++ b/tangem-sdk-old/src/main/res/drawable/id_card_default.xml
@@ -0,0 +1,6 @@
+
+
+
+