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/ui/fragment/MainFragment.kt b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
index ffd3d3f097..965286eda3 100644
--- a/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
@@ -98,6 +98,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
super.onViewCreated(view, savedInstanceState)
rippleBackgroundNfc.startRippleAnimation()
+// navigateToDestination(R.id.action_main_to_emptyIdFragment)
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
@@ -274,13 +275,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..67f1efb3b1
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt
@@ -0,0 +1,465 @@
+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 viewModel: LoadedWalletViewModel
+ 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 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 {
+ // navigateToDestination(R.id.action_loadedWalletFragment_to_main)
+ 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)
+ 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()) {
+
+ Toast.makeText(activity, "ID card: ${cardProtocol.card.idCardData.fullName}", Toast.LENGTH_SHORT).show()
+ 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 {
+ Toast.makeText(activity, "Empty ID card", Toast.LENGTH_SHORT).show()
+ rlToolButtons?.visibility = View.VISIBLE
+ tvBalanceLine1?.text = "Empty ID card".toUpperCase()
+ 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.makeText(activity, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).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 && hasIdInfo != null && hasIdInfo != 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 (::viewModel.isInitialized)
+ viewModel.requestRateInfo(ctx)
+
+ 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..1533b22eb3
--- /dev/null
+++ b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt
@@ -0,0 +1,285 @@
+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 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)
+ 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
+ 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.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() {
+ 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/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt
index c65b0da0e2..26d5df4da2 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 07cb229329..b9bd30a334 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/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java
index d5f5522635..5f2b6d6be2 100644
--- a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java
+++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java
@@ -4,9 +4,6 @@ import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
-import com.google.common.primitives.Bytes;
-import com.tangem.App;
-import com.tangem.Constant;
import com.tangem.data.Blockchain;
import com.tangem.data.network.ServerApiBlockcypher;
import com.tangem.data.network.ServerApiInfura;
@@ -18,11 +15,11 @@ 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.BuildConfig;
import com.tangem.wallet.CoinData;
import com.tangem.wallet.CoinEngine;
import com.tangem.wallet.ECDSASignatureETH;
@@ -32,7 +29,6 @@ import com.tangem.wallet.R;
import com.tangem.wallet.TangemContext;
import com.tangem.wallet.eth.EthData;
-import org.bitcoin.Secp256k1Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.SignatureDecodeException;
import org.bitcoinj.crypto.ChildNumber;
@@ -44,21 +40,19 @@ import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
-import io.github.novacrypto.bip32.ExtendedPublicKey;
-
public class EthIdEngine extends CoinEngine {
private static final String TAG = com.tangem.wallet.eth.EthEngine.class.getSimpleName();
public EthIdData coinData = null;
- String approvalAddress = ""; //TODO
- byte[] approvalPubKey = new byte[1]; //TODO
+ 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 EthData) {
+ } else if (ctx.getCoinData() instanceof EthIdData) {
coinData = (EthIdData) ctx.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName());
@@ -74,29 +68,29 @@ public class EthIdEngine extends CoinEngine {
}
private int getChainIdNum() {
- return ctx.getBlockchain() == Blockchain.Ethereum ? EthTransaction.ChainEnum.Mainnet.getValue() : EthTransaction.ChainEnum.Rinkeby.getValue();
+ return EthTransaction.ChainEnum.Mainnet.getValue();
}
@Override
public boolean awaitingConfirmation() {
- return !coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount()) || App.pendingTransactionsStorage.hasTransactions(ctx.getCard());
+ return false;
}
@Override
public Amount getBalance() {
- if (!hasBalanceInfo()) {
- return null;
- }
- return convertToAmount(coinData.getBalanceInInternalUnits());
+ return null;
}
@Override
public String getBalanceHTML() {
- Amount balance = getBalance();
- if (balance != null) {
- return balance.toDescriptionString(getDecimals());
+ if (coinData != null) {
+ if (coinData.isHasApprovalTx()) {
+ return "VERIFIED";
+ } else {
+ return "NOT REGISTERED";
+ }
} else {
- return "";
+ return "NOT REGISTERED";
}
}
@@ -107,9 +101,7 @@ public class EthIdEngine extends CoinEngine {
@Override
public boolean isBalanceNotZero() {
- if (coinData == null) return false;
- if (coinData.getBalanceInInternalUnits() == null) return false;
- return coinData.getBalanceInInternalUnits().notZero();
+ return true;
}
@Override
@@ -124,7 +116,7 @@ public class EthIdEngine extends CoinEngine {
@Override
public CoinData createCoinData() {
- return new EthData();
+ return new EthIdData();
}
@Override
@@ -132,14 +124,6 @@ public class EthIdEngine extends CoinEngine {
return "";
}
-// BigDecimal convertToEth(String value) {
-// BigInteger m = new BigInteger(value, 10);
-// BigDecimal n = new BigDecimal(m);
-// BigDecimal d = n.divide(new BigDecimal("1000000000000000000"));
-// d = d.setScale(8, RoundingMode.DOWN);
-// return d;
-// }
-
@Override
public boolean validateAddress(String address) {
if (address == null || address.isEmpty()) {
@@ -157,43 +141,6 @@ public class EthIdEngine extends CoinEngine {
return true;
}
-// public String getBalanceValue(TangemCard mCard) {
-// String dec = coinData.getBalanceInInternalUnits();
-// BigDecimal d = convertToEth(dec);
-// String s = d.toString();
-//
-// String pattern = "#0.##################"; // If you like 4 zeros
-// DecimalFormat myFormatter = new DecimalFormat(pattern);
-// String output = myFormatter.format(d);
-// return output;
-// }
-
-// public static String getAmountEquivalentDescription(Amount amount, double rateValue) {
-// if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0)
-// return "";
-//
-// if (rateValue > 0) {
-// BigDecimal biRate = new BigDecimal(rateValue);
-// BigDecimal exchangeCurs = biRate.multiply(amount);
-// exchangeCurs = exchangeCurs.setScale(2, RoundingMode.DOWN);
-// return "≈ USD " + exchangeCurs.toString();
-// } else {
-// return "";
-// }
-// }
-
-// public static String getAmountEquivalentDescriptionETH(Double amount, float rate) {
-// if (amount == 0)
-// return "";
-// amount = amount / 100000;
-// if (rate > 0) {
-// return String.format("≈ USD %.2f", amount * rate);
-// } else {
-// return "";
-// }
-//
-// }
-
@Override
public String getBalanceEquivalent() {
@@ -232,7 +179,7 @@ public class EthIdEngine extends CoinEngine {
@Override
public boolean hasBalanceInfo() {
- return coinData.getBalanceInInternalUnits() != null;
+ return true;
}
@Override
@@ -273,88 +220,27 @@ public class EthIdEngine extends CoinEngine {
@Override
public boolean checkNewTransactionAmount(Amount amount) {
- if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) {
- return true;
- }
- Amount balance = getBalance();
- if (balance == null || amount.compareTo(balance) > 0) {
- return false;
- }
return true;
}
@Override
public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) {
-// Long fee = null;
-// Long amount = null;
-// try {
-// amount = mCard.internalUnitsFromString(amountValue);
-// fee = mCard.internalUnitsFromString(feeValue);
-// } catch (Exception e) {
-// e.printStackTrace();
-// return false;
-// }
-//
-// if (fee == null || amount == null)
-// return false;
-//
-// if (fee == 0 || amount == 0)
-// return false;
-//
-//
-// if (fee < minFeeInInternalUnits)
-// return false;
-
- try {
- BigDecimal cardBalance = getBalance();
-
- if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0))
- return false;
-
- if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0)
- return false;
-
- } catch (NumberFormatException e) {
- e.printStackTrace();
- }
-
return true;
}
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
- if (getBalance() == null) {
+ if (coinData != null && coinData.isHasApprovalTx()) {
+ balanceValidator.setScore(100);
+ balanceValidator.setFirstLine(R.string.verified);
+ balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance);
+ return false;
+ } else {
balanceValidator.setScore(0);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_unknown_balance);
+ balanceValidator.setFirstLine(R.string.not_registered);
balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance);
return false;
}
-
- if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) {
- balanceValidator.setScore(0);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_transaction_in_progress);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_wait_for_confirmation);
- return false;
- }
-
- if (coinData.isBalanceReceived()) {
- balanceValidator.setScore(100);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_balance);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain);
- if (getBalance().isZero()) {
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_empty_wallet);
- balanceValidator.setSecondLine(R.string.empty_string);
- }
- }
-
- if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) {
- balanceValidator.setScore(80);
- balanceValidator.setFirstLine(R.string.balance_validator_first_line_verified_offline);
- balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_verify_online);
- }
-
- return true;
-
}
@Override
@@ -392,29 +278,7 @@ public class EthIdEngine extends CoinEngine {
@Override
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) {
- Log.e(TAG, "Construct transaction " + amountValue.toString() + " with fee " + feeValue.toString() + (IncFee ? " including" : " excluding"));
-
- BigInteger nonceValue = coinData.getConfirmedTXCount();
- byte[] pbKey = ctx.getCard().getWalletPublicKey();
-
- BigInteger weiFee = convertToInternalAmount(feeValue).toBigIntegerExact();
- BigInteger weiAmount = convertToInternalAmount(amountValue).toBigIntegerExact();
-
- if (IncFee) {
- weiAmount = weiAmount.subtract(weiFee);
- }
-
- BigInteger gasPrice = weiFee.divide(BigInteger.valueOf(21000));
- BigInteger gasLimit = BigInteger.valueOf(21000);
- Integer chainId = this.getChainIdNum();
-
- String to = targetAddress;
-
- if (to.startsWith("0x") || to.startsWith("0X")) {
- to = to.substring(2);
- }
-
- final EthTransaction tx = EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
+ final EthTransaction tx = constructIdTxForSign(coinData.getApprovalAddressNonce());
return new SignTask.TransactionToSign() {
@Override
@@ -451,14 +315,14 @@ public class EthIdEngine extends CoinEngine {
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), pbKey);
+ 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, pbKey);
+ 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");
@@ -486,7 +350,7 @@ public class EthIdEngine extends CoinEngine {
//TODO: change request logic, 2000 tx max
if (blockcypherResponse.getTxrefs() != null) {
for (BlockcypherTxref txref : blockcypherResponse.getTxrefs()) {
- serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txref.getTx_hash());
+ serverApiBlockcypher.requestData("ETH", ServerApiBlockcypher.BLOCKCYPHER_TXS, "", txref.getTx_hash());
}
if (serverApiBlockcypher.isRequestsSequenceCompleted()) {
@@ -494,6 +358,8 @@ public class EthIdEngine extends CoinEngine {
} else {
blockchainRequestsCallbacks.onProgress();
}
+ } else {
+ blockchainRequestsCallbacks.onComplete(!ctx.hasError());
}
} catch (Exception e) {
e.printStackTrace();
@@ -516,7 +382,11 @@ public class EthIdEngine extends CoinEngine {
public void onSuccess(BlockcypherTx response) {
Log.i(TAG, "onSuccess: BlockcypherTx");
try {
- if (response.getAddesses().contains(approvalAddress)) {
+ 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) {
@@ -543,7 +413,7 @@ public class EthIdEngine extends CoinEngine {
serverApiBlockcypher.setResponseListener(blockcypherListener);
serverApiBlockcypher.setTxResponseListener(txListener);
- serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
+ serverApiBlockcypher.requestData("ETH", ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
requestApprovalAddressNonce();
}
@@ -599,7 +469,7 @@ public class EthIdEngine extends CoinEngine {
@Override
public void defineWallet() throws CardProtocol.TangemException {
try {
- String wallet = calculateAddress(calculateCKDpub(ctx.getIdHash)); //TODO
+ String wallet = calculateAddress(calculateCKDpub(ctx.getCard().getIdHash()));
ctx.getCoinData().setWallet(wallet);
} catch (Exception e) {
ctx.getCoinData().setWallet("ERROR");
@@ -630,7 +500,7 @@ public class EthIdEngine extends CoinEngine {
to = to.substring(2);
}
- return EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
+ return EthTransaction.create(to, weiAmount, nonceValue, gasPrice, gasLimit, chainId);
}
private boolean checkSignature(byte[] signature, long nonce) throws SignatureDecodeException {
@@ -651,6 +521,7 @@ public class EthIdEngine extends CoinEngine {
Long count = Long.valueOf(pending, 16);
coinData.setApprovalAddressNonce(count);
}
+
@Override
public void onFail(String method, String message) {
Log.e(TAG, "onFail: " + method + " " + message);
@@ -658,6 +529,6 @@ public class EthIdEngine extends CoinEngine {
};
serverApiInfura.setResponseListener(responseListener);
- serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
+ 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..438f604955
--- /dev/null
+++ b/app/src/main/res/layout/fragment_camera.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
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..299fca8b31
--- /dev/null
+++ b/app/src/main/res/layout/fragment_id.xml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..0988c90fd1
--- /dev/null
+++ b/app/src/main/res/layout/layout_id.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..8db274c8b3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -270,6 +270,10 @@
NOT GENUINE
ALREADY CLAIMED
+
+ VERIFIED
+ NOT REGISTERED
+
Save
Delete
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 0fad9c1874..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
@@ -35,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;
@@ -189,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() {
@@ -621,7 +630,32 @@ public class TangemCard {
}
}
- TLVList tlvIDCardData = null;
+ 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) {
@@ -638,8 +672,7 @@ public class TangemCard {
&& tlvIDCardData.hasTag(TLV.Tag.TAG_IssueDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_ExpireDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_TrustedAddress);
}
- public static class IDCardData
- {
+ public static class IDCardData {
public String fullName;
public String birthday;
public String gender;
@@ -649,34 +682,32 @@ public class TangemCard {
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)));
+ 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)));
+ 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();
+ 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;
+ public IDCardData getIDCardData() {
+ if (!hasIDCardData()) return null;
return IDCardData.fromTLVList(tlvIDCardData);
}
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 518318a219..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
@@ -964,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);
+ }
}
/**
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 8f7ef8875f..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++) {
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 2f0cd24013..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.*
@@ -93,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
}
@@ -181,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 @@
+
+
+
+