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 3c4b5285c5..a594c110a8 100644
--- a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
@@ -97,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()
@@ -273,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/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 1f888ee616..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
@@ -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 1f71dc9873..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;
}
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/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/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/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-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 @@
+
+
+
+