Updated on 2026-08-14

This commit is contained in:
Tangem 2019-02-27 22:54:37 +03:00
parent 449840473a
commit 64ddaa4541
63 changed files with 128 additions and 164 deletions

View file

@ -0,0 +1,296 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.text.Editable
import android.text.Html
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.View
import android.widget.Toast
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.event.TransactionFinishWithError
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardcommon.data.TangemCard
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_confirm_transaction.*
import org.greenrobot.eventbus.EventBus
import java.io.IOException
import java.util.*
class ConfirmTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private lateinit var amount: CoinEngine.Amount
private var isIncludeFee: Boolean = true
private var requestPIN2Count = 0
private var nodeCheck = true
private var dtVerified: Date? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_confirm_transaction)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
val engine = CoinEngineFactory.create(ctx)
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(engine!!.balanceHTML)
tvBalance.text = html
isIncludeFee = intent.getBooleanExtra(Constant.EXTRA_FEE_INCLUDED, true)
if (isIncludeFee)
tvIncFee.setText(R.string.including_fee)
else
tvIncFee.setText(R.string.not_including_fee)
amount = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_AMOUNT), intent.getStringExtra(Constant.EXTRA_AMOUNT_CURRENCY))
if (engine.allowSelectFeeInclusion())
tvIncFee.visibility = View.VISIBLE
else
tvIncFee.visibility = View.INVISIBLE
if (ctx.card.blockchainID == Blockchain.Token.id) {
// for Blockchain.Token limit decimals
etAmount.setText(amount.toValueString(ctx.card.tokensDecimal))
} else {
// for others
etAmount.setText(amount.toValueString())
}
tvCurrency.text = engine.balanceCurrency
tvCurrency2.text = engine.feeCurrency
tvCardID.text = ctx.card.cidDescription
etWallet.setText(intent.getStringExtra(Constant.EXTRA_TARGET_ADDRESS))
btnSend.visibility = View.INVISIBLE
if (!engine.allowSelectFeeLevel()) {
rgFee.visibility = View.INVISIBLE
}
// set listeners
rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) }
etFee.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
try {
val eqFee = engine.evaluateFeeEquivalent(etFee!!.text.toString())
tvFeeEquivalent.text = eqFee
if (!ctx.coinData!!.amountEquivalentDescriptionAvailable) {
tvFeeEquivalent.error = getString(R.string.service_unavailable)
tvCurrency2.visibility = View.GONE
tvFeeEquivalent.visibility = View.GONE
} else
tvFeeEquivalent.error = null
} catch (e: Exception) {
e.printStackTrace()
tvFeeEquivalent.text = ""
}
}
override fun afterTextChanged(s: Editable) {
}
})
btnSend.setOnClickListener {
if (UtilHelper.isOnline(this)) {
val calendar = Calendar.getInstance()
calendar.add(Calendar.MINUTE, -1)
if (dtVerified == null || dtVerified!!.before(calendar.time)) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.the_obtained_data_is_outdated_try_again))
return@setOnClickListener
}
val engineCoin = CoinEngineFactory.create(ctx)
if (engineCoin!!.isNeedCheckNode && !nodeCheck) {
Toast.makeText(baseContext, getString(R.string.cannot_reach_current_active_blockchain_node_try_again), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
val txFee = engineCoin.convertToAmount(etFee.text.toString(), tvCurrency2.text.toString())
val txAmount = engineCoin.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
if (!engineCoin.hasBalanceInfo()) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
return@setOnClickListener
} else if (!engineCoin.isBalanceNotZero) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.the_wallet_is_empty))
return@setOnClickListener
} else if (!engineCoin.isExtractPossible) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.please_wait_for_confirmation_of_incoming_transaction))
return@setOnClickListener
}
if (!engineCoin.checkNewTransactionAmountAndFee(txAmount, txFee, isIncludeFee)) {
finishWithError(Activity.RESULT_CANCELED, getString(R.string.not_enough_funds_on_your_card))
return@setOnClickListener
}
requestPIN2Count = 0
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_)
} else
Toast.makeText(this, getString(R.string.no_connection), Toast.LENGTH_SHORT).show()
}
val coinEngine = CoinEngineFactory.create(ctx)
progressBar.visibility = View.VISIBLE
coinEngine!!.requestFee(
object : CoinEngine.BlockchainRequestsCallbacks {
override fun onComplete(success: Boolean) {
if (success) {
onProgress()
progressBar.visibility = View.INVISIBLE
dtVerified = Date()
} else {
finishWithError(Activity.RESULT_CANCELED, ctx.error)
}
}
override fun onProgress() {
doSetFee(rgFee.checkedRadioButtonId)
}
override fun allowAdvance(): Boolean {
return UtilHelper.isOnline(this@ConfirmTransactionActivity)
}
},
etWallet.text.toString(),
amount)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) {
if (data != null && data.extras != null) {
if (data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
}
if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_)
return
}
setResult(resultCode, data)
finish()
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) {
if (resultCode == Activity.RESULT_OK) {
val intent = Intent(baseContext, SignTransactionActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(Constant.EXTRA_AMOUNT, etAmount.text.toString())
intent.putExtra(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
intent.putExtra(Constant.EXTRA_FEE, etFee.text.toString())
intent.putExtra(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
startActivityForResult(intent, Constant.REQUEST_CODE_SIGN_TRANSACTION)
} else
Toast.makeText(baseContext, R.string.pin_2_is_required_to_sign_the_transaction, Toast.LENGTH_LONG).show()
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
val intent = Intent()
setResult(Activity.RESULT_CANCELED, intent)
finish()
return true
}
}
return super.onKeyDown(keyCode, event)
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun doSetFee(checkedRadioButtonId: Int) {
var txtFee = ""
when (checkedRadioButtonId) {
R.id.rbMinimalFee ->
if (ctx.coinData.minFee != null) {
txtFee = ctx.coinData.minFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
R.id.rbNormalFee ->
if (ctx.coinData.normalFee != null) {
txtFee = ctx.coinData.normalFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
R.id.rbMaximumFee ->
if (ctx.coinData.maxFee != null) {
txtFee = ctx.coinData.maxFee!!.toValueString()
btnSend.visibility = View.VISIBLE
} else
btnSend.visibility = View.INVISIBLE
}
etFee.setText(txtFee.replace(',', '.'))
}
private fun finishWithError(errorCode: Int, message: String) {
val transactionFinishWithError = TransactionFinishWithError()
transactionFinishWithError.message = message
EventBus.getDefault().post(transactionFinishWithError)
val intent = Intent()
intent.putExtra("message", message)
setResult(errorCode, intent)
finish()
}
}

View file

@ -0,0 +1,212 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
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 com.tangem.App
import com.tangem.Constant
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.cardandroid.android.nfc.NfcDeviceAntennaLocation
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.asBundle
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.CreateNewWalletTask
import com.tangem.cardcommon.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_create_new_wallet.*
import kotlinx.android.synthetic.main.layout_progress_horizontal.*
import kotlinx.android.synthetic.main.layout_touch_card.*
class CreateNewWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, CreateNewWalletActivity::class.java)
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
return intent
}
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
private var createNewWalletTask: CreateNewWalletTask? = null
private var lastReadSuccess = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create_new_wallet)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(this, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
tvCardId.text = ctx.card!!.cidDescription
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar.visibility = View.INVISIBLE
}
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
// Log.v(TAG, "UID: " + sUID);
if (sUID == ctx.card!!.uid) {
if (lastReadSuccess)
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 5000
else
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
createNewWalletTask = CreateNewWalletTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
createNewWalletTask!!.start()
} else {
// Log.d(TAG, "Mismatch card UID (" + sUID + " instead of " + mCard.getUID() + ")");
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
public override fun onPause() {
createNewWalletTask?.cancel(true)
super.onPause()
}
override fun onReadStart(cardProtocol: CardProtocol) {
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
progressBar?.post {
progressBar?.visibility = View.VISIBLE
progressBar?.progress = 5
}
}
override fun onReadFinish(cardProtocol: CardProtocol?) {
createNewWalletTask = 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)
val intent = Intent()
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(Activity.RESULT_OK, intent)
finish()
}
} else {
lastReadSuccess = false
if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) {
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 intent = Intent()
intent.putExtra("message", "Cannot create wallet. Make sure you enter correct PIN2!")
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card!!.asBundle)
setResult(Constant.RESULT_INVALID_PIN, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
} else {
progressBar!!.post {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allReadyShowed) {
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
}
} else {
Toast.makeText(baseContext, R.string.try_to_scan_again, Toast.LENGTH_SHORT).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 onReadProgress(protocol: CardProtocol, progress: Int) {
progressBar?.post { progressBar?.progress = progress }
}
override fun onReadCancel() {
createNewWalletTask = 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 onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this)
}
}

View file

@ -0,0 +1,257 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.text.Html
import android.view.View
import android.widget.Toast
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.asBundle
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardcommon.data.TangemCard
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.VerifyCardTask
import com.tangem.cardcommon.util.Util
import com.tangem.util.LOG
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_empty_wallet.*
import kotlinx.android.synthetic.main.layout_tangem_card.*
import javax.inject.Inject
class EmptyWalletActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = EmptyWalletActivity::class.java.simpleName
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, EmptyWalletActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
}
@Inject
internal lateinit var navigator: Navigator
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var lastReadSuccess = true
private var verifyCardTask: VerifyCardTask? = null
private var requestPIN2Count = 0
private var cardProtocol: CardProtocol? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_empty_wallet)
App.navigatorComponent?.inject(this)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
tvIssuer.text = ctx.card!!.issuerDescription
if (ctx.card!!.tokenSymbol.length > 1) {
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(ctx.blockchainName)
tvBlockchain.text = html
} else
tvBlockchain.text = ctx.blockchainName
tvCardID.text = ctx.card!!.cidDescription
ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card))
// set listeners
btnNewWallet.setOnClickListener {
requestPIN2Count = 0
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2)
}
btnDetails.setOnClickListener {
if (cardProtocol != null)
navigator.showVerifyCard(this, ctx)
else
UtilHelper.showSingleToast(this, getString(R.string.need_attach_card_again))
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Constant.REQUEST_CODE_CREATE_NEW_WALLET_ACTIVITY) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
data.putExtra("modification", "updateAndViewCard")
data.putExtra("updateDelay", 0)
setResult(Activity.RESULT_OK, data)
}
finish()
} else {
if (data != null && data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", ctx.card!!.uid)
intent.putExtra("Card", ctx.card!!.asBundle)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2)
return
}
}
setResult(resultCode, data)
finish()
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2) {
if (resultCode == Activity.RESULT_OK) {
navigator.showCreateNewWallet(this, ctx)
}
}
}
override fun onTagDiscovered(tag: Tag) {
try {
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
if (ctx.card!!.uid != sUID) {
LOG.d(TAG, "Invalid UID: $sUID")
nfcManager.ignoreTag(isoDep.tag)
return
} else {
LOG.d(TAG, "UID: $sUID")
}
if (lastReadSuccess)
isoDep.timeout = 1000
else
isoDep.timeout = 65000
verifyCardTask = VerifyCardTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, App.firmwaresStorage, this)
verifyCardTask?.start()
} 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 onReadFinish(cardProtocol: CardProtocol?) {
verifyCardTask = 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)
this.cardProtocol = cardProtocol
}
} else {
// remove last UIDs because of error and no card read
progressBar?.post {
lastReadSuccess = false
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allReadyShowed)
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
} else
Toast.makeText(this@EmptyWalletActivity, R.string.try_to_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 onReadProgress(protocol: CardProtocol, progress: Int) {
progressBar?.post { progressBar!!.progress = progress }
}
override fun onReadCancel() {
verifyCardTask = null
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 onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this)
}
}

View file

@ -0,0 +1,46 @@
package com.tangem.ui.activity
import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.fragment.LoadedWallet
import com.tangem.wallet.R
import javax.inject.Inject
class LoadedWalletActivity : AppCompatActivity() {
@Inject
lateinit var navigator: Navigator
companion object {
fun callingIntent(context: Context, lastTag: Tag, ctx: TangemContext): Intent {
val intent = Intent(context, LoadedWalletActivity::class.java)
intent.putExtra(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
ctx.saveToIntent(intent)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_loaded_wallet)
App.navigatorComponent?.inject(this)
if (intent.extras!!.containsKey(NfcAdapter.EXTRA_TAG)) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null) {
val fragment = supportFragmentManager.findFragmentById(R.id.loaded_wallet_fragment) as LoadedWallet
fragment.onTagDiscovered(tag)
}
}
}
}

View file

@ -0,0 +1,57 @@
package com.tangem.ui.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_logo.*
import javax.inject.Inject
class LogoActivity : AppCompatActivity() {
companion object {
fun callingIntent(context: Context, autoHide: Boolean): Intent {
val intent = Intent(context, LogoActivity::class.java)
intent.putExtra(Constant.EXTRA_AUTO_HIDE, autoHide)
return intent
}
}
@Inject
internal lateinit var navigator: Navigator
private val hideRunnable = Runnable { this.hide() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_logo)
App.navigatorComponent?.inject(this)
ivLogo.setOnClickListener { hide() }
}
@SuppressLint("SetTextI18n")
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
if (BuildConfig.DEBUG)
tvAppVersion.text = "BETA v." + BuildConfig.VERSION_NAME + "\n" + "dev" + "\n" + "build " + BuildConfig.VERSION_CODE
else
tvAppVersion.text = "BETA v." + BuildConfig.VERSION_NAME
if (intent.getBooleanExtra(Constant.EXTRA_AUTO_HIDE, true))
ivLogo.postDelayed(hideRunnable, Constant.MILLIS_AUTO_HIDE.toLong())
}
private fun hide() {
navigator.showMain(this)
finish()
}
}

View file

@ -0,0 +1,375 @@
package com.tangem.ui.activity
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import com.scottyab.rootbeer.RootBeer
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Logger
import com.tangem.data.network.ServerApiCommon
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.RootFoundDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.cardandroid.android.nfc.NfcDeviceAntennaLocation
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardandroid.data.saveToBundle
import com.tangem.cardcommon.data.TangemCard
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.ReadCardInfoTask
import com.tangem.util.CommonUtil
import com.tangem.util.LOG
import com.tangem.util.PhoneUtility
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.layout_touch_card.*
import java.io.File
import java.util.*
import javax.inject.Inject
class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications, PopupMenu.OnMenuItemClickListener {
companion object {
val TAG: String = MainActivity::class.java.simpleName
fun callingIntent(context: Context) = Intent(context, MainActivity::class.java)
}
@Inject
internal lateinit var navigator: Navigator
private lateinit var nfcManager: NfcManager
private var zipFile: File? = null
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
private var unsuccessReadCount = 0
private var lastTag: Tag? = null
private var readCardInfoTask: ReadCardInfoTask? = null
private var onNfcReaderCallback: NfcAdapter.ReaderCallback? = null
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null && onNfcReaderCallback != null)
onNfcReaderCallback!!.onTagDiscovered(tag)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
App.navigatorComponent?.inject(this)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
verifyPermissions()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
setNfcAdapterReaderCallback(this)
rippleBackgroundNfc.startRippleAnimation()
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(this, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
// set phone name
if (nfcDeviceAntenna.fullName != "")
tvNFCHint.text = String.format(getString(R.string.scan_banknote), nfcDeviceAntenna.fullName)
else
tvNFCHint.text = String.format(getString(R.string.scan_banknote), getString(R.string.phone))
// NFC
val intent = intent
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null && onNfcReaderCallback != null) {
onNfcReaderCallback?.onTagDiscovered(tag)
}
}
// check if root device
val rootBeer = RootBeer(this)
if (rootBeer.isRootedWithoutBusyBoxCheck && !BuildConfig.DEBUG)
RootFoundDialog().show(supportFragmentManager, RootFoundDialog.TAG)
// set listeners
fab.setOnClickListener { showMenu(it) }
val apiHelper = ServerApiCommon()
apiHelper.setLastVersionListener { response ->
try {
if (response.isNullOrEmpty()) return@setLastVersionListener
val responseVersionName = response.trim(' ', '\n', '\r', '\t')
val responseBuildVersion = responseVersionName.split('.').last()
val appBuildVersion = BuildConfig.VERSION_NAME.split('.').last()
if (responseBuildVersion.toInt() > appBuildVersion.toInt()) Toast.makeText(this, "There is a new application version: $responseVersionName", Toast.LENGTH_LONG).show()
} catch (E: Exception) {
E.printStackTrace()
}
}
apiHelper.requestLastVersion()
}
private fun verifyPermissions() {
NfcManager.verifyPermissions(this)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.e("QRScanActivity", "User hasn't granted permission to use camera")
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), Constant.REQUEST_CODE_REQUEST_CAMERA_PERMISSIONS)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
Constant.REQUEST_CODE_SEND_EMAIL -> {
if (zipFile != null) {
zipFile!!.delete()
zipFile = null
}
}
Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY -> {
if (resultCode == Activity.RESULT_OK && lastTag != null)
onTagDiscovered(lastTag!!)
else
ReadCardInfoTask.resetLastReadInfo()
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
if (BuildConfig.DEBUG) {
for (i in 0 until menu.size()) menu.getItem(i).isVisible = true
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
R.id.sendLogs -> {
var f: File? = null
try {
f = Logger.collectLogs(this)
if (f != null) {
LOG.e(TAG, String.format("Collect %d log bytes", f.length()))
CommonUtil.sendEmail(this, zipFile, TAG, "Logs", PhoneUtility.getDeviceInfo(), arrayOf(f))
} else {
LOG.e(TAG, "Can't create temporarily log file")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (f != null && f.exists())
f.delete()
}
return true
}
R.id.managePIN -> {
navigator.showPinSave(this, false)
return true
}
R.id.managePIN2 -> {
navigator.showPinSave(this, true)
return true
}
R.id.about -> {
navigator.showLogo(this, false)
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
LOG.e(TAG, "setTimeout(" + (1000 + 3000 * unsuccessReadCount) + ")")
if (unsuccessReadCount < 2) {
isoDep.timeout = 2000 + 5000 * unsuccessReadCount
} else {
isoDep.timeout = 90000
}
lastTag = tag
readCardInfoTask = ReadCardInfoTask(NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
readCardInfoTask?.start()
LOG.i(TAG, "onTagDiscovered " + Arrays.toString(tag.id))
} catch (e: Exception) {
e.printStackTrace()
nfcManager.notifyReadResult(false)
}
}
public override fun onResume() {
super.onResume()
nfcDeviceAntenna.animate()
ReadCardInfoTask.resetLastReadInfo()
}
public override fun onPause() {
readCardInfoTask?.cancel(true)
super.onPause()
}
public override fun onStop() {
readCardInfoTask?.cancel(true)
super.onStop()
}
override fun onReadStart(cardProtocol: CardProtocol) {
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
}
override fun onReadProgress(protocol: CardProtocol, progress: Int) {
}
override fun onReadFinish(cardProtocol: CardProtocol?) {
readCardInfoTask = null
if (cardProtocol != null) {
if (cardProtocol.error == null) {
nfcManager.notifyReadResult(true)
rlProgressBar.post {
rlProgressBar.visibility = View.GONE
// TODO - ??? remove save and load???
val cardInfo = Bundle()
cardInfo.putString("UID", cardProtocol.card.uid)
val bCard = Bundle()
cardProtocol.card.saveToBundle(bCard)
cardInfo.putBundle("Card", bCard)
val uid = cardInfo.getString("UID")
val card = TangemCard(uid)
cardInfo.getBundle("Card")?.let { card.loadFromBundle(it) }
val ctx = TangemContext(card)
when {
card.status == TangemCard.Status.Loaded -> lastTag?.let {
val engineCoin = CoinEngineFactory.create(ctx)
?: throw CardProtocol.TangemException("Can't create CoinEngine!")
engineCoin.defineWallet()
navigator.showLoadedWallet(this, it, ctx)
}
card.status == TangemCard.Status.Empty -> navigator.showEmptyWallet(this, ctx)
card.status == TangemCard.Status.Purged -> Toast.makeText(this, R.string.erased_wallet, Toast.LENGTH_SHORT).show()
card.status == TangemCard.Status.NotPersonalized -> Toast.makeText(this, R.string.not_personalized, Toast.LENGTH_SHORT).show()
else -> lastTag?.let { navigator.showLoadedWallet(this, it, ctx) }
}
}
} else {
// remove last UIDs because of error and no card read
rlProgressBar.post {
Toast.makeText(this, R.string.try_to_scan_again, Toast.LENGTH_SHORT).show()
unsuccessReadCount++
if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN)
navigator.showPinRequest(this, PinRequestActivity.Mode.RequestPIN.toString())
else {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported)
if (!NoExtendedLengthSupportDialog.allReadyShowed)
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
lastTag = null
ReadCardInfoTask.resetLastReadInfo()
nfcManager.notifyReadResult(false)
}
}
}
}
rlProgressBar.postDelayed({
try {
rlProgressBar.visibility = View.GONE
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
}
override fun onReadCancel() {
readCardInfoTask = null
ReadCardInfoTask.resetLastReadInfo()
rlProgressBar.postDelayed({
try {
rlProgressBar.visibility = View.GONE
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(Objects.requireNonNull(this), msec)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(Objects.requireNonNull(this), timeout)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(Objects.requireNonNull(this))
}
private fun setNfcAdapterReaderCallback(callback: NfcAdapter.ReaderCallback) {
onNfcReaderCallback = callback
}
private fun showMenu(v: View) {
val popup = PopupMenu(this, v)
val inflater = popup.menuInflater
inflater.inflate(R.menu.menu_main, popup.menu)
if (BuildConfig.DEBUG) {
popup.menu.findItem(R.id.managePIN).isEnabled = true
popup.menu.findItem(R.id.managePIN2).isEnabled = true
popup.menu.findItem(R.id.sendLogs).isVisible = true
}
popup.setOnMenuItemClickListener(this)
popup.show()
}
}

View file

@ -0,0 +1,350 @@
package com.tangem.ui.activity
import android.Manifest
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.fingerprint.FingerprintManager
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.appcompat.app.AppCompatActivity
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import com.tangem.Constant
import com.tangem.data.fingerprint.StartFingerprintReaderTask
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.domain.wallet.TangemContext
import com.tangem.cardandroid.android.data.PINStorage
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardandroid.data.EXTRA_TANGEM_CARD
import com.tangem.cardandroid.data.EXTRA_TANGEM_CARD_UID
import com.tangem.cardcommon.data.TangemCard
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_request.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*
import java.io.IOException
class PinRequestActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, FingerprintHelper.FingerprintHelperListener {
companion object {
val TAG: String = PinRequestActivity::class.java.simpleName
fun callingIntent(context: Activity, mode: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
return intent
}
fun callingIntentRequestPin(context: Activity, mode: String, ctx: TangemContext, newPIN: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentRequestPin2(context: Activity, mode: String, ctx: TangemContext, newPIN2: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentRequestPin2(context: Activity, mode: String, ctx: TangemContext): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
ctx.saveToIntent(intent)
return intent
}
fun callingIntentConfirmPin(context: Activity, mode: String, newPIN: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
return intent
}
fun callingIntentConfirmPin2(context: Activity, mode: String, newPIN2: String): Intent {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, mode)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
return intent
}
}
private lateinit var nfcManager: NfcManager
lateinit var mode: Mode
private var allowFingerprint = false
var startFingerprintReaderTask: StartFingerprintReaderTask? = null
private var fingerprintManager: FingerprintManager? = null
private var fingerprintHelper: FingerprintHelper? = null
enum class Mode {
RequestPIN, RequestPIN2, RequestNewPIN, RequestNewPIN2, ConfirmNewPIN, ConfirmNewPIN2
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_request)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
mode = Mode.valueOf(intent.getStringExtra(Constant.EXTRA_MODE))
if (mode == Mode.RequestNewPIN)
if (PINStorage.haveEncryptedPIN()) {
allowFingerprint = true
tvPinPrompt.setText(R.string.enter_new_pin_or_use_fingerprint_scanner)
} else
tvPinPrompt.setText(R.string.enter_new_pin)
else if (mode == Mode.ConfirmNewPIN)
tvPinPrompt.setText(R.string.confirm_new_pin)
else if (mode == Mode.RequestPIN)
if (PINStorage.haveEncryptedPIN()) {
allowFingerprint = true
tvPinPrompt.setText(R.string.enter_pin_or_use_fingerprint_scanner)
} else
tvPinPrompt.setText(R.string.enter_pin)
else if (mode == Mode.RequestNewPIN2)
if (PINStorage.haveEncryptedPIN2()) {
allowFingerprint = true
tvPinPrompt.setText(R.string.enter_new_pin_2_or_use_fingerprint_scanner)
} else
tvPinPrompt.setText(R.string.enter_new_pin_2)
else if (mode == Mode.ConfirmNewPIN2)
tvPinPrompt.setText(R.string.confirm_new_pin_2)
else if (mode == Mode.RequestPIN2) {
val uid = intent.getStringExtra(EXTRA_TANGEM_CARD_UID)
val card = TangemCard(uid)
card.loadFromBundle(intent.getBundleExtra(EXTRA_TANGEM_CARD))
if (card.PIN2 == TangemCard.PIN2_Mode.DefaultPIN2 || card.PIN2 == TangemCard.PIN2_Mode.Unchecked) {
// if we know PIN2 or not try default previously - use it
PINStorage.setPIN2(PINStorage.getDefaultPIN2())
setResult(Activity.RESULT_OK)
finish()
return
}
if (PINStorage.haveEncryptedPIN2()) {
allowFingerprint = true
tvPinPrompt.setText(R.string.enter_pin_2_or_use_fingerprint_scanner)
} else
tvPinPrompt.setText(R.string.enter_pin_2)
}
if (!allowFingerprint)
tvPinPrompt.visibility = View.GONE
else
tvPinPrompt.visibility = View.VISIBLE
// set listeners
btn0.setOnClickListener { buttonClick(btn0) }
btn1.setOnClickListener { buttonClick(btn1) }
btn2.setOnClickListener { buttonClick(btn2) }
btn3.setOnClickListener { buttonClick(btn3) }
btn4.setOnClickListener { buttonClick(btn4) }
btn5.setOnClickListener { buttonClick(btn5) }
btn6.setOnClickListener { buttonClick(btn6) }
btn7.setOnClickListener { buttonClick(btn7) }
btn8.setOnClickListener { buttonClick(btn8) }
btn9.setOnClickListener { buttonClick(btn9) }
btnBackspace.setOnClickListener {
val s = tvPin!!.text.toString()
if (s.isNotEmpty())
tvPin!!.text = s.substring(0, s.length - 1)
}
btnContinue.setOnClickListener { doContinue() }
}
override fun onPause() {
super.onPause()
fingerprintHelper?.cancel()
if (startFingerprintReaderTask != null) {
startFingerprintReaderTask!!.cancel(true)
startFingerprintReaderTask = null
}
}
override fun onStop() {
super.onStop()
fingerprintHelper?.cancel()
if (startFingerprintReaderTask != null) {
startFingerprintReaderTask!!.cancel(true)
startFingerprintReaderTask = null
}
}
override fun onResume() {
super.onResume()
if (allowFingerprint)
startFingerprintReader()
}
override fun onTagDiscovered(tag: Tag) {
try {
Log.w(javaClass.name, "Ignore discovered tag!")
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun authenticationFailed(error: String) {
LOG.w(TAG, error)
}
@TargetApi(Build.VERSION_CODES.M)
override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
LOG.i(TAG, "Authentication succeeded!")
val cipher = result.cryptoObject.cipher
if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) {
val resultData = Intent()
val pin = PINStorage.loadEncryptedPIN(cipher)
resultData.putExtra("newPIN", pin)
resultData.putExtra("confirmPIN", pin)
setResult(Activity.RESULT_OK, resultData)
finish()
} else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) {
val resultData = Intent()
val pin = PINStorage.loadEncryptedPIN2(cipher)
resultData.putExtra("newPIN2", pin)
resultData.putExtra("confirmPIN2", pin)
setResult(Activity.RESULT_OK, resultData)
finish()
} else if (mode == Mode.RequestPIN) {
PINStorage.loadEncryptedPIN(cipher)
setResult(Activity.RESULT_OK)
} else if (mode == Mode.RequestPIN2) {
PINStorage.loadEncryptedPIN2(cipher)
setResult(Activity.RESULT_OK)
}
finish()
}
private fun buttonClick(button: Button) {
tvPin.text = tvPin.text.toString() + button.text.toString()
}
@SuppressLint("NewApi")
private fun testFingerPrintSettings(): Boolean {
LOG.i(TAG, "Testing Fingerprint Settings")
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
if (!keyguardManager.isKeyguardSecure) {
LOG.i(TAG, "User hasn't enabled Lock Screen")
return false
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
LOG.i(TAG, "User hasn't granted permission to use Fingerprint")
return false
}
if (!fingerprintManager!!.hasEnrolledFingerprints()) {
LOG.i(TAG, "User hasn't registered any fingerprints")
return false
}
LOG.i(TAG, "Fingerprint authentication is set.\n")
return true
}
private fun startFingerprintReader() {
if (!testFingerPrintSettings())
return
if (!allowFingerprint)
return
fingerprintHelper = FingerprintHelper(this@PinRequestActivity)
startFingerprintReaderTask = StartFingerprintReaderTask(this, fingerprintManager, fingerprintHelper)
startFingerprintReaderTask!!.execute(null as Void?)
}
private fun doContinue() {
if (startFingerprintReaderTask != null)
return
// reset errors.
tvPin!!.error = null
// store values at the time of the login attempt.
val pin = tvPin!!.text.toString()
var cancel = false
var focusView: View? = null
if (mode == Mode.ConfirmNewPIN) {
if (pin != intent.getStringExtra("newPIN")) {
tvPin!!.error = getString(R.string.error_pin_confirmation_failed)
focusView = tvPin
cancel = true
}
} else if (mode == Mode.ConfirmNewPIN2) {
if (pin != intent.getStringExtra("newPIN2")) {
tvPin!!.error = getString(R.string.error_pin_confirmation_failed)
focusView = tvPin
cancel = true
}
} else {
if (TextUtils.isEmpty(pin)) {
tvPin!!.error = getString(R.string.error_empty_pin)
focusView = tvPin
cancel = true
}
}
if (cancel)
focusView!!.requestFocus()
else {
if (mode == Mode.RequestNewPIN || mode == Mode.ConfirmNewPIN) {
val resultData = Intent()
resultData.putExtra("newPIN", pin)
if (mode == Mode.ConfirmNewPIN)
resultData.putExtra("confirmPIN", pin)
setResult(Activity.RESULT_OK, resultData)
finish()
} else if (mode == Mode.RequestNewPIN2 || mode == Mode.ConfirmNewPIN2) {
val resultData = Intent()
resultData.putExtra("newPIN2", pin)
if (mode == Mode.ConfirmNewPIN2)
resultData.putExtra("confirmPIN2", pin)
setResult(Activity.RESULT_OK, resultData)
finish()
} else if (mode == Mode.RequestPIN) {
PINStorage.setUserPIN(pin)
setResult(Activity.RESULT_OK)
finish()
} else if (mode == Mode.RequestPIN2) {
PINStorage.setPIN2(pin)
setResult(Activity.RESULT_OK)
finish()
}
}
}
}

View file

@ -0,0 +1,378 @@
package com.tangem.ui.activity
import android.Manifest
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Dialog
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.os.Bundle
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import androidx.core.app.ActivityCompat
import androidx.appcompat.app.AppCompatActivity
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.Toast
import com.tangem.Constant
import com.tangem.data.fingerprint.ConfirmWithFingerprintTask
import com.tangem.data.fingerprint.FingerprintHelper
import com.tangem.cardandroid.android.data.PINStorage
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_save.*
import kotlinx.android.synthetic.main.layout_pin_buttons.*
import java.io.IOException
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.NoSuchPaddingException
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
class PinSaveActivity : AppCompatActivity(), FingerprintHelper.FingerprintHelperListener {
companion object {
val TAG: String = PinSaveActivity::class.java.simpleName
fun callingIntent(context: Context, hasPin2: Boolean): Intent {
val intent = Intent(context, PinSaveActivity::class.java)
intent.putExtra(Constant.EXTRA_PIN2, hasPin2)
return intent
}
}
private var confirmWithFingerprintTask: ConfirmWithFingerprintTask? = null
private var keyStore: KeyStore? = null
private var cipher: Cipher? = null
var fingerprintManager: FingerprintManager? = null
var cryptoObject: FingerprintManager.CryptoObject? = null
var fingerprintHelper: FingerprintHelper? = null
private var usePIN2 = false
private var onConfirmAction: OnConfirmAction? = null
var dFingerPrintConfirmation: Dialog? = null
private enum class OnConfirmAction {
Save, DeleteEncryptedAndSave, Delete
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_save)
usePIN2 = intent.getBooleanExtra(Constant.EXTRA_PIN2, false)
if (usePIN2)
tvPinPrompt.text = getString(R.string.enter_pin2_and_use_fingerprint_to_save_it)
else
tvPinPrompt.text = getString(R.string.enter_pin_and_use_fingerprint_to_save_it)
if (usePIN2) {
cbUseFingerprint.isChecked = true
cbUseFingerprint.isEnabled = false
} else {
cbUseFingerprint.isChecked = PINStorage.haveEncryptedPIN()
cbUseFingerprint.isEnabled = true
}
// set listeners
btn0.setOnClickListener { buttonClick(btn0) }
btn1.setOnClickListener { buttonClick(btn1) }
btn2.setOnClickListener { buttonClick(btn2) }
btn3.setOnClickListener { buttonClick(btn3) }
btn4.setOnClickListener { buttonClick(btn4) }
btn5.setOnClickListener { buttonClick(btn5) }
btn6.setOnClickListener { buttonClick(btn6) }
btn7.setOnClickListener { buttonClick(btn7) }
btn8.setOnClickListener { buttonClick(btn8) }
btn9.setOnClickListener { buttonClick(btn9) }
btnBackspace.setOnClickListener {
val s = tvPin.text.toString()
if (s.isNotEmpty())
tvPin.text = s.substring(0, s.length - 1)
}
btnSavePIN.setOnClickListener { doSavePIN() }
btnDeletePIN.setOnClickListener { doDeletePIN() }
}
override fun onPause() {
super.onPause()
if (fingerprintHelper != null)
fingerprintHelper!!.cancel()
if (confirmWithFingerprintTask != null)
confirmWithFingerprintTask!!.cancel(true)
}
override fun onStop() {
super.onStop()
if (fingerprintHelper != null)
fingerprintHelper!!.cancel()
if (confirmWithFingerprintTask != null)
confirmWithFingerprintTask!!.cancel(true)
}
override fun authenticationFailed(error: String) {
}
@TargetApi(Build.VERSION_CODES.M)
override fun authenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
cipher = result.cryptoObject.cipher
when (onConfirmAction) {
PinSaveActivity.OnConfirmAction.Save -> {
val textToEncrypt = tvPin.text.toString()
if (usePIN2) {
PINStorage.saveEncryptedPIN2(cipher, textToEncrypt)
} else {
PINStorage.saveEncryptedPIN(cipher, textToEncrypt)
}
}
PinSaveActivity.OnConfirmAction.Delete -> {
if (usePIN2) {
PINStorage.deleteEncryptedPIN2()
} else {
PINStorage.deleteEncryptedPIN()
PINStorage.deletePIN()
}
tvPin.text = ""
}
PinSaveActivity.OnConfirmAction.DeleteEncryptedAndSave -> if (usePIN2) {
PINStorage.deleteEncryptedPIN2()
PINStorage.saveEncryptedPIN2(cipher, tvPin.text.toString())
} else {
PINStorage.deleteEncryptedPIN()
PINStorage.savePIN(tvPin.text.toString())
}
}
dFingerPrintConfirmation!!.dismiss()
finish()
}
fun getKeyStore(): Boolean {
try {
keyStore = KeyStore.getInstance(Constant.KEYSTORE)
// create empty keystore
keyStore!!.load(null)
return true
} catch (e: KeyStoreException) {
e.printStackTrace()
} catch (e: CertificateException) {
e.printStackTrace()
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
@TargetApi(Build.VERSION_CODES.M)
fun createNewKey(forceCreate: Boolean): Boolean {
try {
if (forceCreate)
keyStore!!.deleteEntry(Constant.KEY_ALIAS)
if (!keyStore!!.containsAlias(Constant.KEY_ALIAS)) {
val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, Constant.KEYSTORE)
generator.init(KeyGenParameterSpec.Builder(Constant.KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.build()
)
generator.generateKey()
}
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
@SuppressLint("InlinedApi")
fun getCipher(): Boolean {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7)
return true
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
} catch (e: NoSuchPaddingException) {
e.printStackTrace()
}
return false
}
@TargetApi(Build.VERSION_CODES.M)
fun initCipher(mode: Int): Boolean {
try {
keyStore!!.load(null)
val keySpec = keyStore!!.getKey(Constant.KEY_ALIAS, null) as SecretKey
if (mode == Cipher.ENCRYPT_MODE) {
cipher!!.init(mode, keySpec)
} else {
val iv = PINStorage.loadEncryptedIV()
val ivSpec = IvParameterSpec(iv)
cipher!!.init(mode, keySpec, ivSpec)
}
return true
} catch (e: KeyPermanentlyInvalidatedException) {
e.printStackTrace()
// retry after clearing entry
createNewKey(true)
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
@TargetApi(Build.VERSION_CODES.M)
fun initCryptObject(): Boolean {
try {
cryptoObject = FingerprintManager.CryptoObject(cipher!!)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
@SuppressLint("SetTextI18n")
private fun buttonClick(button: Button) {
tvPin.text = String.format("%s%s", tvPin.text, button.text)
}
private fun doSavePIN() {
if (confirmWithFingerprintTask != null) {
return
}
tvPin.error = null
val pin = tvPin.text.toString()
var cancel = false
var focusView: View? = null
if (TextUtils.isEmpty(pin)) {
tvPin.error = getString(R.string.error_empty_pin)
focusView = tvPin
cancel = true
}
if (cancel) {
focusView!!.requestFocus()
} else {
if (usePIN2) {
if (!testFingerPrintSettings()) {
tvPin.postDelayed({ this.finish() }, 2000)
return
}
onConfirmAction = OnConfirmAction.Save
confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveActivity)
confirmWithFingerprintTask!!.execute(null as Void?)
} else {
if (cbUseFingerprint.isChecked || PINStorage.haveEncryptedPIN()) {
if (!testFingerPrintSettings()) {
tvPin.postDelayed({ this.finish() }, 2000)
return
}
onConfirmAction = if (cbUseFingerprint.isChecked) {
OnConfirmAction.Save
} else {
OnConfirmAction.DeleteEncryptedAndSave
}
// show a progress spinner, and kick off a background task to perform the user login attempt
confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveActivity)
confirmWithFingerprintTask!!.execute(null as Void?)
} else {
PINStorage.savePIN(tvPin.text.toString())
finish()
}
}
}
}
private fun doDeletePIN() {
if (usePIN2) {
if (PINStorage.haveEncryptedPIN2()) {
if (!testFingerPrintSettings()) {
tvPin.postDelayed({ this.finish() }, 2000)
return
}
onConfirmAction = OnConfirmAction.Delete
confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveActivity)
confirmWithFingerprintTask!!.execute(null as Void?)
} else {
tvPin.text = ""
finish()
}
} else {
if (cbUseFingerprint.isChecked || PINStorage.haveEncryptedPIN()) {
if (!testFingerPrintSettings()) {
tvPin.postDelayed({ this.finish() }, 2000)
return
}
onConfirmAction = OnConfirmAction.Delete
confirmWithFingerprintTask = ConfirmWithFingerprintTask(this@PinSaveActivity)
confirmWithFingerprintTask!!.execute(null as Void?)
} else {
tvPin.text = ""
PINStorage.deletePIN()
finish()
}
}
}
@SuppressLint("NewApi")
private fun testFingerPrintSettings(): Boolean {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
if (!keyguardManager.isKeyguardSecure) {
Toast.makeText(baseContext, R.string.user_has_not_enabled_lock_screen, Toast.LENGTH_LONG).show()
return false
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(baseContext, R.string.user_has_not_granted_permission_to_use_fingerprint, Toast.LENGTH_LONG).show()
return false
}
if (!fingerprintManager!!.hasEnrolledFingerprints()) {
Toast.makeText(baseContext, R.string.user_has_not_registered_any_fingerprints, Toast.LENGTH_LONG).show()
return false
}
return true
}
}

View file

@ -0,0 +1,205 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import com.tangem.App
import com.tangem.Constant
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.*
import com.tangem.cardcommon.data.TangemCard
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.SwapPINTask
import com.tangem.cardcommon.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_pin_swap.*
class PinSwapActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = PinSwapActivity::class.java.simpleName
fun callingIntent(context: Context, newPIN: String, newPIN2: String): Intent {
val intent = Intent(context, PinSwapActivity::class.java)
intent.putExtra(Constant.EXTRA_NEW_PIN, newPIN)
intent.putExtra(Constant.EXTRA_NEW_PIN_2, newPIN2)
return intent
}
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
}
private lateinit var nfcManager: NfcManager
private var card: TangemCard? = null
private var newPIN: String? = null
private var newPIN2: String? = null
private var progressBar: ProgressBar? = null
private var swapPinTask: SwapPINTask? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pin_swap)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
card = TangemCard(intent.getStringExtra(EXTRA_TANGEM_CARD_UID))
card!!.loadFromBundle(intent.extras!!.getBundle(EXTRA_TANGEM_CARD))
newPIN = intent.getStringExtra(Constant.EXTRA_NEW_PIN)
newPIN2 = intent.getStringExtra(Constant.EXTRA_NEW_PIN_2)
tvCardID.text = card!!.cidDescription
progressBar = findViewById(R.id.progressBar)
progressBar!!.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar!!.visibility = View.INVISIBLE
}
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
LOG.d(TAG, "UID: $sUID")
if (sUID == card!!.uid) {
isoDep.timeout = card!!.pauseBeforePIN2 + 65000
swapPinTask = SwapPINTask(card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, newPIN, newPIN2)
swapPinTask!!.start()
} else {
LOG.d(TAG, "Mismatch card UID (" + sUID + " instead of " + card!!.uid + ")")
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
public override fun onPause() {
swapPinTask?.cancel(true)
super.onPause()
}
public override fun onStop() {
swapPinTask?.cancel(true)
super.onStop()
}
override fun onReadStart(cardProtocol: CardProtocol) {
progressBar?.post {
progressBar?.visibility = View.VISIBLE
progressBar?.progress = 5
}
}
override fun onReadFinish(cardProtocol: CardProtocol?) {
swapPinTask = null
if (cardProtocol != null) {
when {
cardProtocol.error == null -> progressBar!!.post {
progressBar!!.progress = 100
progressBar!!.progressTintList = ColorStateList.valueOf(Color.GREEN)
val intent = Intent()
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(Activity.RESULT_OK, intent)
finish()
}
cardProtocol.error is CardProtocol.TangemException_InvalidPIN -> {
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 intent = Intent()
intent.putExtra("message", "Cannot change PIN(s). Make sure you enter correct PIN2!")
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(RESULT_INVALID_PIN, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
}
else -> progressBar!!.post {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allReadyShowed) {
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
}
} else {
Toast.makeText(baseContext, R.string.try_to_scan_again, Toast.LENGTH_SHORT).show()
}
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
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
}
}
override fun onReadProgress(protocol: CardProtocol, progress: Int) {
progressBar!!.post { progressBar!!.progress = progress }
}
override fun onReadCancel() {
swapPinTask = 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 onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(this, msec)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this)
}
}

View file

@ -0,0 +1,176 @@
package com.tangem.ui.activity
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.View
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.data.network.CryptonitOtherApi
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_cryptonit_other_api_withdrawal.*
import java.io.IOException
import javax.inject.Inject
class PrepareCryptonitOtherApiWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareCryptonitOtherApiWithdrawalActivity::class.java.simpleName
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var cryptonit: CryptonitOtherApi? = null
@Inject
internal lateinit var navigator: Navigator
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_cryptonit_other_api_withdrawal)
App.navigatorComponent?.inject(this)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
cryptonit = CryptonitOtherApi(this)
tvKey.text = cryptonit!!.key
tvUserID.text = cryptonit!!.userId
tvSecret.text = cryptonit!!.secretDescription
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString())
etAmount.filters = engine.amountInputFilters
// set listeners
btnLoad.setOnClickListener {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
// if (!engine.checkAmount(card, strAmount))
// etAmount.error = getString(R.string.unknown_amount_format)
val dblAmount: Double = strAmount.toDouble()
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
cryptonit!!.requestCryptoWithdrawal(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
//Toast.makeText(this, strAmount, Toast.LENGTH_LONG).show()
// val balance = engine.getBalanceLong(card)!! / (card!!.blockchain.multiplier / 1000.0)
// if (etAmount.text.toString().replace(",", ".").toDouble() > balance) {
// etAmount.error = getString(R.string.not_enough_funds_on_your_account)
// return@setOnClickListener
// }
}
ivCameraKey.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_KEY) }
ivCameraSecret.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_SECRET) }
ivCameraUserId.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR_USER_ID) }
ivRefreshBalance.setOnClickListener { doRequestBalance() }
cryptonit!!.setBalanceListener { response ->
when (ctx.blockchain) {
Blockchain.Ethereum, Blockchain.EthereumTestNet -> {
tvBalance.text = response.eth_available
}
Blockchain.Bitcoin, Blockchain.BitcoinTestNet, Blockchain.BitcoinCash -> {
tvBalance.text = response.btc_available
}
else -> {
}
}
tvBalanceCurrency.text = ctx.blockchain.currency
tvBalance.setTextColor(Color.BLACK)
rlProgressBar.visibility = View.INVISIBLE
btnLoad.isActivated = true
}
cryptonit!!.setErrorListener { throwable ->
throwable.printStackTrace()
rlProgressBar.visibility = View.INVISIBLE
tvError.visibility = View.VISIBLE
tvError.text = throwable.message
}
cryptonit!!.setWithdrawalListener { response ->
rlProgressBar.visibility = View.INVISIBLE
if (response.success != null && response.success!!) finish()
else {
tvError.visibility = View.VISIBLE
tvError.text = response.reason!!.toString()
}
}
btnLoad.isActivated = false
doRequestBalance()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
when (requestCode) {
Constant.REQUEST_CODE_SCAN_QR_KEY -> {
cryptonit!!.key = data.getStringExtra("QRCode")
tvKey!!.text = cryptonit!!.key
}
Constant.REQUEST_CODE_SCAN_QR_SECRET -> {
cryptonit!!.secret = data.getStringExtra("QRCode")
tvSecret!!.text = cryptonit!!.secretDescription
}
Constant.REQUEST_CODE_SCAN_QR_USER_ID -> {
cryptonit!!.userId = data.getStringExtra("QRCode")
tvUserID!!.text = cryptonit!!.userId
}
}
cryptonit!!.saveAccountInfo()
doRequestBalance()
}
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun doRequestBalance() {
if (cryptonit!!.havaAccountInfo()) {
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_balance)
tvError.visibility = View.INVISIBLE
cryptonit!!.requestBalance(ctx.blockchain.currency, "USD")
} else {
tvError.visibility = View.VISIBLE
tvError.text = getString(R.string.cryptonit_not_enough_account_data)
}
}
}

View file

@ -0,0 +1,160 @@
package com.tangem.ui.activity
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.text.InputFilter
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.data.network.Cryptonit
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.util.DecimalDigitsInputFilter
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_cryptonit_withdrawal.*
import java.io.IOException
class PrepareCryptonitWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareCryptonitWithdrawalActivity::class.java.simpleName
}
private lateinit var ctx: TangemContext
private lateinit var nfcManager: NfcManager
private var cryptonit: Cryptonit? = null
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_cryptonit_withdrawal)
ctx = TangemContext.loadFromBundle(this, intent.extras)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
cryptonit = Cryptonit(this)
etUsername.setText(cryptonit!!.username)
etPassword.setText(cryptonit!!.password)
etFee.setText(cryptonit!!.fee)
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
tvFeeCurrency.text = engine.feeCurrency
etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString())
etAmount.filters = engine.amountInputFilters
etAmount.setOnEditorActionListener { lv, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(lv.windowToken, 0)
true
} else
false
}
when (ctx.blockchain) {
Blockchain.Bitcoin -> {
etAmount.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(5))
etFee.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(5))
}
Blockchain.BitcoinCash -> {
etAmount.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(8))
etFee.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(8))
}
Blockchain.Ethereum -> {
etAmount.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(18))
etFee.filters = arrayOf<InputFilter>(DecimalDigitsInputFilter(18))
}
else -> {
}
}
// set listeners
btnLoad.setOnClickListener {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
val strFee: String = etFee.text.toString().replace(",", ".")
val dblAmount: Double = strAmount.toDouble()
cryptonit!!.fee = strFee
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_withdrawal)
cryptonit!!.requestWithdrawCoins(ctx.blockchain.currency, dblAmount, ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
}
ivRefreshBalance.setOnClickListener {
cryptonit!!.username = etUsername.text.toString()
cryptonit!!.password = etPassword.text.toString()
cryptonit!!.saveAccountInfo()
doRequestBalance()
}
cryptonit!!.setBalanceListener { response ->
tvBalance.text = response.results[0].balance.toString()
tvBalanceCurrency.text = response.results[0].currency // card!!.blockchain.currency
tvBalance.setTextColor(Color.BLACK)
rlProgressBar.visibility = View.INVISIBLE
btnLoad.visibility = View.VISIBLE
}
cryptonit!!.setErrorListener { throwable ->
throwable.printStackTrace()
rlProgressBar.visibility = View.INVISIBLE
tvError.visibility = View.VISIBLE
tvError.text = throwable.message
}
cryptonit!!.setWithdrawalListener { response ->
rlProgressBar.visibility = View.INVISIBLE
if (response.success != null && response.success!!) {
Toast.makeText(this, R.string.withdrawal_successful, Toast.LENGTH_LONG).show();
finish()
} else {
tvError.visibility = View.VISIBLE
tvError.text = response.errors.toString()
}
}
btnLoad.visibility = View.INVISIBLE
doRequestBalance()
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun doRequestBalance() {
if (cryptonit!!.haveAccountInfo()) {
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.cryptonit_request_balance)
tvError.visibility = View.INVISIBLE
cryptonit!!.requestBalance(ctx.blockchain.currency)
} else {
tvError.visibility = View.VISIBLE
tvError.text = getString(R.string.cryptonit_not_enough_account_data)
}
}
}

View file

@ -0,0 +1,258 @@
package com.tangem.ui.activity
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Color
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.network.Kraken
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.data.Blockchain
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_kraken_withdrawal.*
import java.io.IOException
import java.math.BigDecimal
import java.net.URI
import java.util.*
import javax.inject.Inject
class PrepareKrakenWithdrawalActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareKrakenWithdrawalActivity::class.java.simpleName
}
private lateinit var ctx: TangemContext
private lateinit var nfcManager: NfcManager
private var kraken: Kraken? = null
private var fee: BigDecimal? = null
@Inject
internal lateinit var navigator: Navigator
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_kraken_withdrawal)
App.navigatorComponent?.inject(this)
ctx = TangemContext.loadFromBundle(this, intent.extras)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
kraken = Kraken(this)
tvKey.text = kraken!!.key
tvSecret.text = kraken!!.secretDescription
tvCardID.text = ctx.card!!.cidDescription
tvWallet.text = ctx.coinData!!.wallet
val engine = CoinEngineFactory.create(ctx)
tvCurrency.text = engine!!.balanceCurrency
etAmount.setText(engine.convertToAmount(engine.convertToInternalAmount(ctx.card!!.denomination)).toValueString())
etAmount.filters = engine.amountInputFilters
etAmount.setOnEditorActionListener { lv, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(lv.windowToken, 0)
lv.clearFocus()
true
} else {
false
}
}
// set listeners
btnLoad.setOnClickListener {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
var dblAmount: Double = strAmount.toDouble()
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
kraken!!.requestWithdrawInfo(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
}
ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) }
ivRefreshBalance.setOnClickListener { doRequestBalance() }
kraken!!.setBalanceListener { response ->
if (response.error != null && response.error.isNotEmpty()) {
tvError.visibility = View.VISIBLE
tvError.text = Arrays.toString(response.error)
} else {
when (ctx.blockchain) {
Blockchain.Ethereum -> {
tvBalance.text = response.result.XETH.trimEnd('0')
}
Blockchain.Bitcoin -> {
tvBalance.text = response.result.XXBT.trimEnd('0')
}
Blockchain.BitcoinCash -> {
tvBalance.text = response.result.BCH.trimEnd('0')
}
else -> {
tvBalance.text = "???"
}
}
tvBalanceCurrency.text = ctx.blockchain.currency
tvBalance.setTextColor(Color.BLACK)
btnLoad.visibility = View.VISIBLE
}
rlProgressBar.visibility = View.INVISIBLE
}
kraken!!.setErrorListener { throwable ->
throwable.printStackTrace()
rlProgressBar.visibility = View.INVISIBLE
tvError.visibility = View.VISIBLE
tvError.text = throwable.message
}
kraken!!.setWithdrawalListener { response ->
rlProgressBar.visibility = View.INVISIBLE
if (response.error != null && response.error.isNotEmpty()) {
tvError.visibility = View.VISIBLE
tvError.text = Arrays.toString(response.error)
} else {
Toast.makeText(this, "Withdrawal successful!", Toast.LENGTH_LONG).show()
finish()
}
}
kraken!!.setWithdrawalInfoListener { response ->
rlProgressBar.visibility = View.INVISIBLE
if (response.error != null && response.error.isNotEmpty()) {
tvError.visibility = View.VISIBLE
tvError.text = Arrays.toString(response.error)
} else {
fee = BigDecimal(response.result.fee)
showConfirmDialog()
}
}
btnLoad.visibility = View.INVISIBLE
doRequestBalance()
}
// Method to show an alert dialog with yes, no and cancel button
private fun showConfirmDialog() {
// Late initialize an alert dialog object
lateinit var dialog: AlertDialog
// Initialize a new instance of alert dialog builder object
val builder = AlertDialog.Builder(this)
// Set a title for alert dialog
builder.setTitle(R.string.please_confirm_withdraw)
// Set a message for alert dialog
builder.setMessage(String.format("Continue with fee %s %s?", fee!!.toString().trimEnd('0'), ctx.blockchain.currency))
// On click listener for dialog buttons
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
try {
val strAmount: String = etAmount.text.toString().replace(",", ".")
var dblAmount: Double = strAmount.toDouble()
dblAmount += fee!!.toDouble()
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.kraken_request_withdrawal)
kraken!!.requestWithdraw(ctx.blockchain.currency, dblAmount.toString(), ctx.coinData!!.wallet)
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
}
DialogInterface.BUTTON_NEGATIVE -> {
Toast.makeText(this, R.string.operation_canceled, Toast.LENGTH_LONG).show()
}
}
}
// Set the alert dialog positive/yes button
builder.setPositiveButton(R.string.yes, dialogClickListener)
// Set the alert dialog negative/no button
builder.setNegativeButton(R.string.no, dialogClickListener)
// Initialize the AlertDialog using builder object
dialog = builder.create()
// Finally, display the alert dialog
dialog.show()
}
private fun doRequestBalance() {
if (kraken!!.haveAccountInfo()) {
rlProgressBar.visibility = View.VISIBLE
tvProgressDescription.text = getString(R.string.kraken_request_balance)
tvError.visibility = View.INVISIBLE
kraken!!.requestBalance()
} else {
tvError.visibility = View.VISIBLE
tvError.text = getString(R.string.kraken_not_enough_account_data)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
when (requestCode) {
Constant.REQUEST_CODE_SCAN_QR -> {
val uri = URI(data.getStringExtra("QRCode"))
val query = uri.query
val params = query.split("&")
for (param in params) {
if (param.startsWith("key=")) kraken!!.key = param.substring(4)
else if (param.startsWith("secret=")) kraken!!.secret = param.substring(7)
}
tvKey!!.text = kraken!!.key
tvSecret!!.text = kraken!!.secretDescription
}
}
kraken!!.saveAccountInfo()
doRequestBalance()
}
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
}

View file

@ -0,0 +1,178 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.di.Navigator
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_prepare_transaction.*
import java.io.IOException
import javax.inject.Inject
class PrepareTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = PrepareTransactionActivity::class.java.simpleName
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, PrepareTransactionActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
}
@Inject
internal lateinit var navigator: Navigator
private lateinit var ctx: TangemContext
private lateinit var nfcManager: NfcManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_prepare_transaction)
App.navigatorComponent?.inject(this)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
tvCardID.text = ctx.card?.cidDescription
val engine = CoinEngineFactory.create(ctx)
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(engine!!.balanceHTML)
tvBalance.text = html
if (!engine.allowSelectFeeInclusion()) {
rgIncFee.visibility = View.INVISIBLE
} else {
rgIncFee.visibility = View.VISIBLE
}
if (ctx.card!!.remainingSignatures < 2)
etAmount.isEnabled = false
tvCurrency.text = engine.balance.currency
etAmount.setText(engine.balance.toValueString())
// limit number of symbols after comma
etAmount.filters = engine.amountInputFilters
// set listeners
etAmount.setOnEditorActionListener { lv, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(lv.windowToken, 0)
lv.clearFocus()
true
} else {
false
}
}
btnVerify.setOnClickListener {
if (!UtilHelper.isOnline(this)) {
Toast.makeText(this, R.string.no_connection, Toast.LENGTH_LONG).show()
return@setOnClickListener
}
val engine1 = CoinEngineFactory.create(ctx)
val strAmount: String = etAmount.text.toString().replace(",", ".")
val amount = engine1!!.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
try {
if (!engine.checkNewTransactionAmount(amount))
etAmount.error = getString(R.string.not_enough_funds_on_your_card)
else
etAmount.error = null
} catch (e: Exception) {
etAmount.error = getString(R.string.unknown_amount_format)
}
// check wallet address
if (!engine1.validateAddress(etWallet.text.toString())) {
etWallet.error = getString(R.string.incorrect_destination_wallet_address)
return@setOnClickListener
} else
etWallet.error = null
if (etWallet.text.toString() == ctx.coinData!!.wallet) {
etWallet.error = getString(R.string.destination_wallet_address_equal_source_address)
return@setOnClickListener
}
if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) {
return@setOnClickListener
}
val intent = Intent(baseContext, ConfirmTransactionActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
intent.putExtra(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
intent.putExtra(Constant.EXTRA_AMOUNT, strAmount)
intent.putExtra(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
startActivityForResult(intent, Constant.REQUEST_CODE_SEND_TRANSACTION__)
}
ivCamera.setOnClickListener { navigator.showQrScanActivity(this, Constant.REQUEST_CODE_SCAN_QR) }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.extras!!.containsKey("QRCode")) {
var code = data.getStringExtra("QRCode")
when (ctx.blockchain) {
Blockchain.Bitcoin -> {
if (code.contains("bitcoin:")) {
val tmp = code.split("bitcoin:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
code = tmp[1]
}
}
Blockchain.Ethereum, Blockchain.Token -> {
if (code.contains("ethereum:")) {
val tmp = code.split("ethereum:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
code = tmp[1]
} else if (code.contains("blockchain:")) {
val tmp = code.split("blockchain:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
code = tmp[1]
}
}
else -> {
}
}
etWallet?.setText(code)
} else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) {
setResult(resultCode, data)
finish()
}
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
}

View file

@ -0,0 +1,239 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.ColorStateList
import android.graphics.Color
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 com.tangem.App
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialogNew
import com.tangem.ui.event.DeletingWalletFinish
import com.tangem.cardandroid.android.nfc.NfcDeviceAntennaLocation
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.asBundle
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.PurgeTask
import com.tangem.cardcommon.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_purge.*
import kotlinx.android.synthetic.main.layout_touch_card.*
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
class PurgeActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = PurgeActivity::class.java.simpleName
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, PurgeActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
const val RESULT_INVALID_PIN = Activity.RESULT_FIRST_USER
}
@Inject
internal lateinit var waitSecurityDelayDialogNew: WaitSecurityDelayDialogNew
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
private var purgeTask: PurgeTask? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_purge)
App.navigatorComponent?.inject(this)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(this, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
tvCardID.text = ctx.card!!.cidDescription
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar.visibility = View.INVISIBLE
}
public override fun onStop() {
purgeTask?.cancel(true)
super.onStop()
}
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
LOG.d(TAG, "UID: $sUID")
if (sUID == ctx.card!!.uid) {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
purgeTask = PurgeTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this)
purgeTask!!.start()
} else {
LOG.d(TAG, "Mismatch card UID (" + sUID + " instead of " + ctx.card.uid + ")")
nfcManager.ignoreTag(isoDep.tag)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(this, msec)
// val readWait = ReadWait()
// readWait.msec = msec
// EventBus.getDefault().post(readWait)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
// if (!waitSecurityDelayDialogNew.isAdded)
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
//
//
// val readBeforeRequest = ReadBeforeRequest()
// readBeforeRequest.timeout = timeout
// EventBus.getDefault().post(readBeforeRequest)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(this)
// val readAfterRequest = ReadAfterRequest()
// EventBus.getDefault().post(readAfterRequest)
}
override fun onReadStart(cardProtocol: CardProtocol) {
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
progressBar.post {
progressBar.visibility = View.VISIBLE
progressBar.progress = 5
}
}
override fun onReadFinish(cardProtocol: CardProtocol?) {
purgeTask = 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)
val intent = Intent()
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(Activity.RESULT_OK, intent)
EventBus.getDefault().post(DeletingWalletFinish())
finish()
}
} else {
if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) {
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 intent = Intent()
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
intent.putExtra("message", getString(R.string.cannot_erase_wallet))
setResult(RESULT_INVALID_PIN, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
} else {
progressBar!!.post {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allReadyShowed)
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
} else
Toast.makeText(baseContext, R.string.try_to_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 onReadProgress(protocol: CardProtocol, progress: Int) {
progressBar?.post { progressBar?.progress = progress }
}
override fun onReadCancel() {
purgeTask = null
progressBar?.postDelayed({
try {
progressBar?.progress = 0
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar?.visibility = View.INVISIBLE
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
}
}

View file

@ -0,0 +1,72 @@
package com.tangem.ui.activity
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.appcompat.app.AppCompatActivity
import com.google.zxing.Result
import com.tangem.Constant
import me.dm7.barcodescanner.zxing.ZXingScannerView
class QrScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
companion object {
fun callingIntent(context: Context): Intent {
return Intent(context, QrScanActivity::class.java)
}
}
private var scannerView: ZXingScannerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1)
else
runScanner()
}
override fun onPause() {
super.onPause()
scannerView?.stopCamera()
}
override fun onResume() {
super.onResume()
scannerView?.startCamera()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
runScanner()
else {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
}
}
override fun handleResult(result: Result) {
val data = Intent()
data.putExtra(Constant.EXTRA_QR_CODE, result.text)
setResult(Activity.RESULT_OK, data)
finish()
}
private fun runScanner() {
// programmatically initialize the scanner view
scannerView = ZXingScannerView(this)
setContentView(scannerView)
// register ourselves as a handler for scan results.
scannerView?.setResultHandler(this)
scannerView?.startCamera()
}
}

View file

@ -0,0 +1,102 @@
package com.tangem.ui.activity
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.KeyEvent
import android.widget.Toast
import com.tangem.Constant
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.event.TransactionFinishWithError
import com.tangem.ui.event.TransactionFinishWithSuccess
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import org.greenrobot.eventbus.EventBus
import java.io.IOException
class SendTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var tx: ByteArray? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_send_transaction)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
tx = intent.getByteArrayExtra(Constant.EXTRA_TX)
val engine = CoinEngineFactory.create(ctx)
engine!!.requestSendTransaction(
object : CoinEngine.BlockchainRequestsCallbacks {
override fun onComplete(success: Boolean) {
if (success)
finishWithSuccess()
else
finishWithError(ctx.error)
}
override fun onProgress() {
}
override fun allowAdvance(): Boolean {
return UtilHelper.isOnline(this@SendTransactionActivity)
}
},
tx
)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
Toast.makeText(this, R.string.please_wait, Toast.LENGTH_LONG).show()
return true
}
}
return super.onKeyDown(keyCode, event)
}
override fun onTagDiscovered(tag: Tag) {
try {
nfcManager.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun finishWithSuccess() {
val transactionFinishWithSuccess = TransactionFinishWithSuccess()
transactionFinishWithSuccess.message = getString(R.string.transaction_has_been_successfully_signed)
EventBus.getDefault().post(transactionFinishWithSuccess)
val intent = Intent()
intent.putExtra(Constant.EXTRA_MESSAGE, getString(R.string.transaction_has_been_successfully_signed))
setResult(RESULT_OK, intent)
finish()
}
private fun finishWithError(message: String) {
val transactionFinishWithError = TransactionFinishWithError()
transactionFinishWithError.message = String.format(getString(R.string.try_again_failed_to_send_transaction), message)
EventBus.getDefault().post(transactionFinishWithError)
val intent = Intent()
intent.putExtra(Constant.EXTRA_MESSAGE, String.format(getString(R.string.try_again_failed_to_send_transaction), message))
setResult(RESULT_CANCELED, intent)
finish()
}
}

View file

@ -0,0 +1,302 @@
package com.tangem.ui.activity
import android.app.Activity
import android.content.Intent
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.KeyEvent
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.cardandroid.android.nfc.NfcDeviceAntennaLocation
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.asBundle
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.SignTask
import com.tangem.cardcommon.util.Util
import com.tangem.util.LOG
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_sign_transaction.*
import kotlinx.android.synthetic.main.layout_progress_horizontal.*
import kotlinx.android.synthetic.main.layout_touch_card.*
class SignTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtocol.Notifications {
companion object {
val TAG: String = SignTransactionActivity::class.java.simpleName
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private lateinit var mpFinishSignSound: MediaPlayer
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
private var signTransactionTask: SignTask? = 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)
setContentView(R.layout.activity_sign_transaction)
nfcManager = NfcManager(this, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
ctx = TangemContext.loadFromBundle(this, intent.extras)
mpFinishSignSound = MediaPlayer.create(this, R.raw.scan_card_sound)
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(this, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
amount = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_AMOUNT), intent.getStringExtra(Constant.EXTRA_AMOUNT_CURRENCY))
fee = CoinEngine.Amount(intent.getStringExtra(Constant.EXTRA_FEE), intent.getStringExtra(Constant.EXTRA_FEE_CURRENCY))
isIncludeFee = intent.getBooleanExtra(Constant.EXTRA_FEE_INCLUDED, true)
outAddressStr = intent.getStringExtra(Constant.EXTRA_TARGET_ADDRESS)
tvCardID.text = ctx.card!!.cidDescription
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
progressBar.visibility = View.INVISIBLE
}
public override fun onPause() {
signTransactionTask?.cancel(true)
super.onPause()
}
public override fun onStop() {
signTransactionTask?.cancel(true)
super.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
setResult(resultCode, data)
finish()
return
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
val intent = Intent()
setResult(Activity.RESULT_CANCELED, intent)
finish()
return true
}
}
return super.onKeyDown(keyCode, event)
}
override fun onTagDiscovered(tag: Tag) {
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag.id
val sUID = Util.byteArrayToHexString(uid)
if (sUID == ctx.card!!.uid) {
if (lastReadSuccess) {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 5000
} else {
isoDep.timeout = ctx.card!!.pauseBeforePIN2 + 65000
}
val coinEngine = CoinEngineFactory.create(ctx)
?: throw CardProtocol.TangemException("Can't create CoinEngine!")
coinEngine.setOnNeedSendTransaction { tx ->
if (tx != null) {
val intent = Intent(this, SendTransactionActivity::class.java)
ctx.saveToIntent(intent)
intent.putExtra(Constant.EXTRA_TX, tx)
startActivityForResult(intent, Constant.REQUEST_CODE_SEND_TRANSACTION_)
}
}
val transactionToSign = coinEngine.constructTransaction(amount, fee, isIncludeFee, outAddressStr)
signTransactionTask = SignTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, this, transactionToSign)
signTransactionTask!!.start()
} else
nfcManager.ignoreTag(isoDep.tag)
} catch (e: CardProtocol.TangemException_WrongAmount) {
try {
val intent = Intent()
intent.putExtra("message", getString(R.string.cannot_sign_transaction_wrong_amount))
intent.putExtra("UID", ctx.card.uid)
intent.putExtra("Card", ctx.card.asBundle)
setResult(Activity.RESULT_CANCELED, intent)
finish()
} 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 intent = Intent()
intent.putExtra("message", getString(R.string.cannot_sign_transaction_make_sure_you_enter_correct_pin_2))
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(Constant.RESULT_INVALID_PIN_, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}, 500)
} else {
if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
try {
val intent = Intent()
intent.putExtra("message", getString(R.string.cannot_sign_transaction_wrong_amount))
intent.putExtra("UID", cardProtocol.card.uid)
intent.putExtra("Card", cardProtocol.card.asBundle)
setResult(Activity.RESULT_CANCELED, intent)
finish()
} catch (e: Exception) {
e.printStackTrace()
}
}
progressBar!!.post {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
if (!NoExtendedLengthSupportDialog.allReadyShowed) {
NoExtendedLengthSupportDialog.message = getText(R.string.the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.the_nfc_adapter_length_apdu_advice).toString()
NoExtendedLengthSupportDialog().show(supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
}
} else {
Toast.makeText(baseContext, R.string.try_to_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)
}
// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew()
override fun onReadBeforeRequest(timeout: Int) {
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
WaitSecurityDelayDialog.onReadBeforeRequest(this, timeout)
// if (!waitSecurityDelayDialogNew.isAdded)
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
// val readBeforeRequest = ReadBeforeRequest()
// readBeforeRequest.timeout = timeout
// EventBus.getDefault().post(readBeforeRequest)
}
override fun onReadAfterRequest() {
LOG.i(TAG, "onReadAfterRequest")
WaitSecurityDelayDialog.onReadAfterRequest(this)
// val readAfterRequest = ReadAfterRequest()
// EventBus.getDefault().post(readAfterRequest)
}
override fun onReadWait(msec: Int) {
LOG.i(TAG, "onReadWait msec $msec")
WaitSecurityDelayDialog.onReadWait(this, msec)
// val readWait = ReadWait()
// readWait.msec = msec
// EventBus.getDefault().post(readWait)
}
}

View file

@ -0,0 +1,43 @@
package com.tangem.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.App
import com.tangem.Constant
import com.tangem.di.Navigator
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.fragment.VerifyCard
import com.tangem.wallet.R
import javax.inject.Inject
class VerifyCardActivity : AppCompatActivity() {
@Inject
lateinit var navigator: Navigator
companion object {
fun callingIntent(context: Context, ctx: TangemContext): Intent {
val intent = Intent(context, VerifyCardActivity::class.java)
ctx.saveToIntent(intent)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_verify_card)
App.navigatorComponent?.inject(this)
}
override fun onBackPressed() {
super.onBackPressed()
val verifyCard = supportFragmentManager.findFragmentById(R.id.verify_card_fragment) as VerifyCard
val data = verifyCard.prepareResultIntent()
data.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE)
finish()
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.ui.dialog
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.tangem.wallet.R
class NoExtendedLengthSupportDialog : DialogFragment() {
companion object {
val TAG: String = NoExtendedLengthSupportDialog::class.java.simpleName
var allReadyShowed = false
var message = ""// = R.string.the_nfc_adapter_length_apdu
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(activity)
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.warning)
.setMessage(message)
.setPositiveButton(R.string.got_it) { _, _ -> NoExtendedLengthSupportDialog.allReadyShowed = false }
.create()
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.tangem.wallet.R;
public class PINSwapWarningDialog extends DialogFragment {
public static final String TAG = PINSwapWarningDialog.class.getSimpleName();
public static final String EXTRA_MESSAGE = "message";
private String message;
private OnPositiveButton mOnPositiveButton;
public interface OnPositiveButton {
void onRefresh();
}
public void setOnRefreshPage(OnPositiveButton listener) {
mOnPositiveButton = listener;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
message = getArguments().getString(EXTRA_MESSAGE);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.your_money_is_at_risk)
.setMessage(message)
.setCancelable(true)
.setNegativeButton(R.string.cancel, (dialog, which) -> dismiss())
.setPositiveButton(R.string.contin, (dialog, whichButton) -> mOnPositiveButton.onRefresh())
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.ui.dialog
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.tangem.wallet.R
class RootFoundDialog : DialogFragment() {
companion object {
val TAG: String = RootFoundDialog::class.java.simpleName
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(activity)
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.device_is_rooted)
.setCancelable(false)
.setPositiveButton(R.string.got_it, null)
.create()
}
}

View file

@ -0,0 +1,78 @@
package com.tangem.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.tangem.util.UtilHelper;
import com.tangem.wallet.R;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
/**
* Created by dvol on 06.03.2018.
*/
public class ShowQRCodeDialog extends DialogFragment {
private ImageView ivQR;
private TextView tvQRaddress;
private Bitmap bmQR;
private String addr;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
View v = inflater.inflate(R.layout.dialog_show_qr, null);
ivQR = v.findViewById(R.id.ivQR);
ivQR.setImageBitmap(bmQR);
tvQRaddress = v.findViewById(R.id.tvQRaddress);
tvQRaddress.setText(addr);
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.show_wallet_qr_code)
.setView(v)
.setPositiveButton(R.string.ok, (dialog,which)->dismiss() )
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
}
public void setup(String content) {
try {
bmQR = UtilHelper.INSTANCE.generateQrCode(content);
addr = content;
} catch (Exception e) {
e.printStackTrace();
}
}
public static void show(final AppCompatActivity activity, final String content) {
activity.runOnUiThread(() -> {
ShowQRCodeDialog instance = new ShowQRCodeDialog();
instance.setup(content);
instance.show(activity.getSupportFragmentManager(), "ShowQRCodeDialog");
});
}
}

View file

@ -0,0 +1,157 @@
package com.tangem.ui.dialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ProgressBar;
import com.tangem.wallet.R;
import java.util.Timer;
import java.util.TimerTask;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
/**
* Created by dvol on 06.03.2018.
*/
public class WaitSecurityDelayDialog extends DialogFragment {
public static final String TAG = WaitSecurityDelayDialog.class.getSimpleName();
private ProgressBar progressBar;
private int msTimeout = 60000, msProgress = 0;
private Timer timer;
private static Timer timerToShowDelayDialog = null;
private static WaitSecurityDelayDialog instance = null;
private final static int minRemainingDelayToShowDialog = 1000;
private final static int delayBeforeShowDialog = 5000;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
View v = inflater.inflate(R.layout.dialog_wait_pin2, null);
progressBar = v.findViewById(R.id.progressBar);
progressBar.setMax(msTimeout);
progressBar.setProgress(msProgress);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (progress < WaitSecurityDelayDialog.this.progressBar.getMax()) {
WaitSecurityDelayDialog.this.progressBar.setProgress(progress + 1000);
}
});
}
}, 1000, 1000);
return new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.security_delay)
.setView(v)
.setCancelable(false)
.create();
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
}
public static void onReadBeforeRequest(final AppCompatActivity activity, final int timeout) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog != null || timeout < delayBeforeShowDialog + minRemainingDelayToShowDialog)
return;
timerToShowDelayDialog = new Timer();
timerToShowDelayDialog.schedule(new TimerTask() {
@Override
public void run() {
if (WaitSecurityDelayDialog.instance != null) return;
instance = new WaitSecurityDelayDialog();
instance.setup(timeout, delayBeforeShowDialog);
instance.setCancelable(false);
instance.show(activity.getSupportFragmentManager(), TAG);
}
}, delayBeforeShowDialog);
});
}
public static void onReadAfterRequest(final Activity activity) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog == null) return;
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
});
}
public static void onReadWait(final AppCompatActivity activity, final int msec) {
activity.runOnUiThread(() -> {
if (timerToShowDelayDialog != null) {
timerToShowDelayDialog.cancel();
timerToShowDelayDialog = null;
}
if (msec == 0) {
if (instance != null && instance.isAdded()) {
instance.dismiss();
instance = null;
}
return;
}
if (instance == null) {
if (msec > minRemainingDelayToShowDialog) {
instance = new WaitSecurityDelayDialog();
// 1000ms - card delay notification interval
instance.setup(msec + 1000, 1000);
instance.setCancelable(false);
instance.show(activity.getSupportFragmentManager(), TAG);
}
} else
instance.setRemainingTimeout(msec);
});
}
private void setup(int msTimeout, int msProgress) {
this.msTimeout = msTimeout;
this.msProgress = msProgress;
}
private void setRemainingTimeout(final int msec) {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress)
progressBar.setProgress(newProgress);
else
progressBar.setMax(progress + msec);
}
});
}
}

View file

@ -0,0 +1,156 @@
package com.tangem.ui.dialog
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatDialogFragment
import android.widget.ProgressBar
import com.tangem.ui.event.ReadAfterRequest
import com.tangem.ui.event.ReadBeforeRequest
import com.tangem.ui.event.ReadWait
import com.tangem.util.LOG
import com.tangem.wallet.R
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.util.*
class WaitSecurityDelayDialogNew : AppCompatDialogFragment() {
companion object {
val TAG: String = WaitSecurityDelayDialogNew::class.java.simpleName
private const val MIN_REMAINING_DELAY_TO_SHOW_DIALOG = 1000
private const val DELAY_BEFORE_SHOW_DIALOG = 5000
}
private lateinit var pb: ProgressBar
private var msTimeout = 60000
private var msProgress = 0
private var timer: Timer? = null
private var timerToShowDelayDialog: Timer? = null
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater = activity!!.layoutInflater
val v = inflater.inflate(R.layout.dialog_wait_pin2, null)
pb = v.findViewById(R.id.progressBar)
pb.max = msTimeout
pb.progress = msProgress
timer = Timer()
timer!!.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
pb.post {
val progress = pb.progress
if (progress < pb.max)
pb.progress = progress + 1000
}
}
}, 1000, 1000)
return AlertDialog.Builder(activity)
.setIcon(R.drawable.tangem_logo_small_new)
.setTitle(R.string.security_delay)
.setView(v)
.setCancelable(false)
.create()
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
@Subscribe
fun readBeforeRequest(readBeforeRequest: ReadBeforeRequest) {
LOG.i(TAG, "readBeforeRequest 111")
if (timerToShowDelayDialog != null || readBeforeRequest.timeout!! < DELAY_BEFORE_SHOW_DIALOG + MIN_REMAINING_DELAY_TO_SHOW_DIALOG)
return
timerToShowDelayDialog = Timer()
timerToShowDelayDialog!!.schedule(object : TimerTask() {
override fun run() {
setup(readBeforeRequest.timeout!!, DELAY_BEFORE_SHOW_DIALOG)
isCancelable = false
// if (!isAdded)
show(activity!!.supportFragmentManager, TAG)
// if (isHidden)
// activity?.supportFragmentManager?.let { show(it, TAG) }
}
}, DELAY_BEFORE_SHOW_DIALOG.toLong())
}
@Subscribe
fun readAfterRequest(readAfterRequest: ReadAfterRequest) {
LOG.i(TAG, "readAfterRequest 222")
if (timerToShowDelayDialog == null)
return
timerToShowDelayDialog!!.cancel()
timerToShowDelayDialog = null
}
@Subscribe
fun readWait(readWait: ReadWait) {
LOG.i(TAG, "readWait 333")
if (timerToShowDelayDialog != null) {
timerToShowDelayDialog!!.cancel()
timerToShowDelayDialog = null
}
if (readWait.msec == 0) {
dismiss()
return
}
if (readWait.msec!! > MIN_REMAINING_DELAY_TO_SHOW_DIALOG) {
// 1000ms - card delay notification interval
setup(readWait.msec!! + 1000, 1000)
isCancelable = false
if (isHidden)
activity?.supportFragmentManager?.let { show(it, TAG) }
} else
setRemainingTimeout(readWait.msec!!)
}
private fun setup(msTimeout: Int, msProgress: Int) {
this.msTimeout = msTimeout
this.msProgress = msProgress
}
private fun setRemainingTimeout(msec: Int) {
pb.post {
val progress = pb.progress
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
pb.max = progress + msec
timer!!.cancel()
timer = null
} else {
val newProgress = pb.max - msec
if (pb.max > progress)
pb.progress = newProgress
else
pb.max = progress + msec
}
}
}
}

View file

@ -0,0 +1,3 @@
package com.tangem.ui.event
class DeletingWalletFinish

View file

@ -0,0 +1,3 @@
package com.tangem.ui.event
class ReadAfterRequest

View file

@ -0,0 +1,5 @@
package com.tangem.ui.event
class ReadBeforeRequest {
var timeout: Int? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.ui.event
class ReadWait {
var msec: Int? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.ui.event
class TransactionFinishWithError {
var message: String? = null
}

View file

@ -0,0 +1,5 @@
package com.tangem.ui.event
class TransactionFinishWithSuccess {
var message: String? = null
}

View file

@ -0,0 +1,802 @@
package com.tangem.ui.fragment
import android.app.Activity
import android.app.AlertDialog
import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.media.MediaPlayer
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.domain.wallet.BalanceValidator
import com.tangem.domain.wallet.CoinEngine
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.activity.LoadedWalletActivity
import com.tangem.ui.activity.PinRequestActivity
import com.tangem.ui.activity.PrepareCryptonitWithdrawalActivity
import com.tangem.ui.activity.PrepareKrakenWithdrawalActivity
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.PINSwapWarningDialog
import com.tangem.ui.dialog.ShowQRCodeDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.ui.event.DeletingWalletFinish
import com.tangem.ui.event.TransactionFinishWithError
import com.tangem.ui.event.TransactionFinishWithSuccess
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.android.reader.NfcReader
import com.tangem.cardandroid.data.EXTRA_TANGEM_CARD
import com.tangem.cardandroid.data.EXTRA_TANGEM_CARD_UID
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardcommon.data.TangemCard
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.cardcommon.tasks.VerifyCardTask
import com.tangem.cardcommon.util.Util
import com.tangem.serverandroid.ServerApiTangem
import com.tangem.serverandroid.model.CardVerifyAndGetInfo
import com.tangem.util.LOG
import com.tangem.util.UtilHelper
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_loaded_wallet.*
import kotlinx.android.synthetic.main.layout_btn_details.*
import kotlinx.android.synthetic.main.layout_tangem_card.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.io.InputStream
import java.util.*
import kotlin.concurrent.timerTask
class LoadedWallet : androidx.fragment.app.Fragment(), NfcAdapter.ReaderCallback, CardProtocol.Notifications, SharedPreferences.OnSharedPreferenceChangeListener {
companion object {
val TAG: String = LoadedWallet::class.java.simpleName
}
private lateinit var ctx: TangemContext
private lateinit var nfcManager: NfcManager
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 cardProtocol: CardProtocol? = null
private val inactiveColor: ColorStateList by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
resources.getColorStateList(R.color.btn_dark, activity?.theme)
else
@Suppress("DEPRECATION")
resources.getColorStateList(R.color.btn_dark)
}
private val activeColor: ColorStateList by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
resources.getColorStateList(R.color.colorAccent, activity?.theme)
else
@Suppress("DEPRECATION")
resources.getColorStateList(R.color.colorAccent)
}
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(activity, activity?.intent?.extras)
nfcManager = NfcManager(activity!!, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
lastTag = activity?.intent?.getParcelableExtra(Constant.EXTRA_LAST_DISCOVERED_TAG)
mpSecondScanSound = MediaPlayer.create(activity, R.raw.scan_card_sound)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fr_loaded_wallet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val engine = CoinEngineFactory.create(ctx)
tvBalance.setSingleLine(!engine!!.needMultipleLinesForBalance())
ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card))
btnExtract.isEnabled = false
btnExtract.backgroundTintList = inactiveColor
tvWallet.text = ctx.coinData.wallet
// set listeners
srl.setOnRefreshListener { refresh() }
tvWallet.setOnClickListener { doShareWallet(false) }
btnExplore.setOnClickListener { startActivity(Intent(Intent.ACTION_VIEW, engine?.walletExplorerUri)) }
btnCopy.setOnClickListener { doShareWallet(false) }
btnLoad.setOnClickListener {
val items = arrayOf<CharSequence>(getString(R.string.in_app), getString(R.string.load_via_share_address), getString(R.string.load_via_qr))//, getString(R.string.via_cryptonit), getString(R.string.via_kraken))
val cw = android.view.ContextThemeWrapper(activity, R.style.AlertDialogTheme)
val dialog = AlertDialog.Builder(cw).setItems(items
) { _, which ->
when (items[which]) {
getString(R.string.in_app) -> {
try {
val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUri)
intent.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(intent)
} catch (e: ActivityNotFoundException) {
UtilHelper.showSingleToast(context, getString(R.string.no_compatible_wallet))
}
}
getString(R.string.load_via_share_address) -> {
doShareWallet(true)
}
getString(R.string.load_via_qr) -> {
ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUri.toString())
}
getString(R.string.via_cryptonit) -> {
val intent = Intent(activity, PrepareCryptonitWithdrawalActivity::class.java)
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_TRANSACTION)
}
getString(R.string.via_kraken) -> {
val intent = Intent(activity, PrepareKrakenWithdrawalActivity::class.java)
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_RECEIVE_TRANSACTION)
}
else -> {
}
}
}
val dlg = dialog.show()
val wlp = dlg.window.attributes
wlp.gravity = Gravity.BOTTOM
dlg.window.attributes = wlp
}
btnExtract.setOnClickListener {
if (UtilHelper.isOnline(context as Activity))
if (!engine.isExtractPossible)
UtilHelper.showSingleToast(context, ctx.message)
else if (ctx.card!!.remainingSignatures == 0)
UtilHelper.showSingleToast(context, getString(R.string.card_has_no_remaining_signature))
else
(activity as LoadedWalletActivity).navigator.showPrepareTransaction(context as Activity, ctx)
else
Toast.makeText(activity, getString(R.string.no_connection), Toast.LENGTH_SHORT).show()
}
btnDetails.setOnClickListener {
if (cardProtocol != null)
(activity as LoadedWalletActivity).navigator.showVerifyCard(context as Activity, ctx)
else
UtilHelper.showSingleToast(context, getString(R.string.need_attach_card_again))
}
btnNewScan.setOnClickListener { (activity as LoadedWalletActivity).navigator.showMain(context as Activity) }
// 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()
}
if (result.artwork != null && App.localStorage.checkNeedUpdateArtwork(result.artwork)) {
LOG.w(TAG, "Artwork '${result.artwork!!.id}' updated, need download")
requestCounter++
serverApiTangem.requestArtwork(result.artwork!!.id, result.artwork!!.getUpdateDate(), ctx.card!!)
updateViews()
}
}
override fun onFail(message: String?) {
LOG.i(TAG, "cardVerifyAndGetInfoListener onFail")
if (activity == null || !UtilHelper.isOnline(activity!!)) return
requestCounter--
updateViews()
}
}
serverApiTangem.setCardVerifyAndGetInfoListener(cardVerifyAndGetInfoListener)
// request artwork listener
val artworkListener: ServerApiTangem.ArtworkListener = object : ServerApiTangem.ArtworkListener {
override fun onSuccess(artworkId: String?, inputStream: InputStream?, updateDate: Date?) {
LOG.i(TAG, "artworkListener onSuccess")
if (activity == null || !UtilHelper.isOnline(activity!!)) return
App.localStorage.updateArtwork(artworkId!!, inputStream!!, updateDate!!)
requestCounter--
ivTangemCard.setImageBitmap(App.localStorage.getCardArtworkBitmap(ctx.card!!))
updateViews()
}
override fun onFail(message: String?) {
LOG.i(TAG, "artworkListener onFail")
if (activity == null || !UtilHelper.isOnline(activity!!)) return
requestCounter--
updateViews()
}
}
serverApiTangem.setArtworkListener(artworkListener)
// request rate info listener
serverApiCommon.setRateInfoListener {
if (activity == null || !UtilHelper.isOnline(activity!!)) return@setRateInfoListener
val rate = it.priceUsd.toFloat()
ctx.coinData!!.rate = rate
ctx.coinData!!.rateAlter = rate
}
refresh()
startVerify(lastTag)
}
override fun onPause() {
super.onPause()
if (timerHideErrorAndMessage != null) {
timerHideErrorAndMessage!!.cancel()
timerHideErrorAndMessage = null
}
}
override fun onStart() {
super.onStart()
if (!EventBus.getDefault().isRegistered(this))
EventBus.getDefault().register(this)
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Subscribe
fun onTransactionFinishWithSuccess(transactionFinishWithSuccess: TransactionFinishWithSuccess) {
ctx.message = transactionFinishWithSuccess.message
ctx.coinData.clearInfo()
updateViews()
srl?.isRefreshing = true
srl?.postDelayed({ refresh() }, 5000)
}
@Subscribe
fun onTransactionFinishWithError(transactionFinishWithError: TransactionFinishWithError) {
ctx.error = transactionFinishWithError.message
updateViews()
}
@Subscribe
fun onDeleteWalletFinish(deletingWalletFinish: DeletingWalletFinish) {
activity?.finish()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
Constant.REQUEST_CODE_VERIFY_CARD ->
// action after erase wallet
if (resultCode == Activity.RESULT_OK)
activity?.finish()
Constant.REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK)
if (data != null)
if (data.extras != null && data.extras!!.containsKey(Constant.EXTRA_CONFIRM_PIN))
(activity as LoadedWalletActivity).navigator.showPinRequestRequestPin(context as Activity, PinRequestActivity.Mode.RequestPIN.toString(), ctx, data.getStringExtra(Constant.EXTRA_NEW_PIN))
else
(activity as LoadedWalletActivity).navigator.showPinRequestConfirmNewPin(context as Activity, PinRequestActivity.Mode.ConfirmNewPIN.toString(), data.getStringExtra(Constant.EXTRA_NEW_PIN))
Constant.REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK)
if (data != null)
if (data.extras != null && data.extras!!.containsKey(Constant.EXTRA_CONFIRM_PIN_2))
(activity as LoadedWalletActivity).navigator.showPinRequestRequestPin2(context as Activity, PinRequestActivity.Mode.RequestPIN2.toString(), ctx, data.getStringExtra(Constant.EXTRA_NEW_PIN_2))
else
(activity as LoadedWalletActivity).navigator.showPinRequestConfirmNewPin2(context as Activity, PinRequestActivity.Mode.ConfirmNewPIN2.toString(), data.getStringExtra(Constant.EXTRA_NEW_PIN_2))
Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (newPIN == "")
newPIN = ctx.card!!.pin
if (newPIN2 == "")
newPIN2 = App.pinStorage.piN2
val pinSwapWarningDialog = PINSwapWarningDialog()
pinSwapWarningDialog.setOnRefreshPage { (activity as LoadedWalletActivity).navigator.showPinSwap(context as Activity, newPIN, newPIN2) }
val bundle = Bundle()
if (!CardProtocol.isDefaultPIN(newPIN) || !CardProtocol.isDefaultPIN2(newPIN2))
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_forget))
else
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_use_default))
pinSwapWarningDialog.arguments = bundle
activity?.supportFragmentManager?.let { pinSwapWarningDialog.show(it, PINSwapWarningDialog.TAG) }
}
Constant.REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (data == null) {
ctx.saveToIntent(data)
data?.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_DELETE)
} else
data.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE)
activity?.setResult(Activity.RESULT_OK, data)
activity?.finish()
} else {
if (data != null && data.extras != null && data.extras!!.containsKey(EXTRA_TANGEM_CARD_UID) && data.extras!!.containsKey(EXTRA_TANGEM_CARD)) {
val updatedCard = TangemCard(data.getStringExtra(EXTRA_TANGEM_CARD_UID))
updatedCard.loadFromBundle(data.getBundleExtra(EXTRA_TANGEM_CARD))
ctx.card = updatedCard
}
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
(activity as LoadedWalletActivity).navigator.showPinRequestRequestPin2(context as Activity, PinRequestActivity.Mode.RequestPIN2.toString(), ctx)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
ctx.error = data.getStringExtra("message")
}
}
}
Constant.REQUEST_CODE_PURGE -> if (resultCode == Activity.RESULT_OK) {
if (data == null) {
ctx.saveToIntent(data)
data?.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_DELETE)
} else
data.putExtra(Constant.EXTRA_MODIFICATION, Constant.EXTRA_MODIFICATION_UPDATE)
activity?.setResult(Activity.RESULT_OK, data)
activity?.finish()
} else {
if (data != null && data.extras != null && data.extras!!.containsKey(EXTRA_TANGEM_CARD_UID) && data.extras!!.containsKey(EXTRA_TANGEM_CARD)) {
val updatedCard = TangemCard(data.getStringExtra(EXTRA_TANGEM_CARD_UID))
updatedCard.loadFromBundle(data.getBundleExtra(EXTRA_TANGEM_CARD))
ctx.card = updatedCard
}
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(activity, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
ctx.error = data.getStringExtra("message")
}
}
updateViews()
}
Constant.REQUEST_CODE_SEND_TRANSACTION, Constant.REQUEST_CODE_RECEIVE_TRANSACTION -> {
if (resultCode == Activity.RESULT_OK) {
ctx.coinData?.clearInfo()
srl?.postDelayed({ this.refresh() }, 5000)
srl?.isRefreshing = true
updateViews()
}
if (data != null && data.extras != null) {
if (data.extras!!.containsKey(EXTRA_TANGEM_CARD_UID) && data.extras!!.containsKey(EXTRA_TANGEM_CARD)) {
val updatedCard = TangemCard(data.getStringExtra(EXTRA_TANGEM_CARD_UID))
updatedCard.loadFromBundle(data.getBundleExtra(EXTRA_TANGEM_CARD))
ctx.card = updatedCard
}
}
}
}
}
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()
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.try_to_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) return
if (timerHideErrorAndMessage != null) {
timerHideErrorAndMessage!!.cancel()
timerHideErrorAndMessage = null
}
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) {
tvBalanceLine1.setTextColor(resources.getColor(R.color.primary))
tvBalanceLine1.text = getString(R.string.verifying_in_blockchain)
tvBalanceLine2.text = ""
tvBalance.text = ""
tvBalanceEquivalent.text = ""
} else {
val validator = BalanceValidator()
// TODO why attest=false?
validator.check(ctx, false)
context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1.setTextColor(it) }
tvBalanceLine1.text = validator.firstLine
tvBalanceLine2.text = validator.getSecondLine(false)
}
val engine = CoinEngineFactory.create(ctx)
when {
engine!!.hasBalanceInfo() -> {
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(engine.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(engine.balanceHTML)
tvBalance.text = html
tvBalanceEquivalent.text = engine.balanceEquivalent
}
ctx.card?.offlineBalance != null -> {
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(engine.offlineBalanceHTML, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(engine.offlineBalanceHTML)
tvBalance.text = html
}
else -> tvBalance.text = getString(R.string.no_data_string)
}
tvWallet.text = ctx.coinData!!.wallet
if (ctx.card!!.tokenSymbol.length > 1) {
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(ctx.blockchainName)
tvBlockchain.text = html
} else
tvBlockchain.text = ctx.blockchainName
if (requestCounter == 0 && engine.hasBalanceInfo()) {
btnExtract.isEnabled = true
btnExtract.backgroundTintList = activeColor
} else {
btnExtract.isEnabled = false
btnExtract.backgroundTintList = inactiveColor
}
if (engine.isNftToken) {
btnLoad.isEnabled = false
btnLoad.backgroundTintList = inactiveColor
btnExtract.isEnabled = false
btnExtract.backgroundTintList = inactiveColor
}
// //TODO why ???
// ctx.error = null
// ctx.message = null
}
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
if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash) {
ctx.coinData.setIsBalanceEqual(true)
}
requestVerifyAndGetInfo()
requestBalanceAndUnspentTransactions()
requestRateInfo()
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)
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.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.no_connection)
updateViews()
}
}
private fun requestRateInfo() {
if (UtilHelper.isOnline(context as Activity)) {
LOG.i(TAG, "requestRateInfo")
// TODO - move requestRateInfo to CoinEngine
val cryptoId: String = when (ctx.blockchain) {
Blockchain.Bitcoin -> "bitcoin"
Blockchain.BitcoinTestNet -> "bitcoin"
Blockchain.Ethereum -> "ethereum"
Blockchain.EthereumTestNet -> "ethereum"
Blockchain.Token -> "ethereum"
Blockchain.NftToken -> "ethereum"
Blockchain.BitcoinCash -> "bitcoin-cash"
Blockchain.Litecoin -> "litecoin"
Blockchain.Rootstock -> "bitcoin"
Blockchain.RootstockToken -> "bitcoin"
Blockchain.Cardano -> "cardano"
else -> {
throw Exception("Can''t get rate for blockchain " + ctx.blockchainName)
}
}
serverApiCommon.requestRateInfo(cryptoId)
} else {
ctx.error = getString(R.string.no_connection)
updateViews()
}
}
private fun startVerify(tag: Tag?) {
try {
val isoDep = IsoDep.get(tag)
?: throw CardProtocol.TangemException(getString(R.string.wrong_tag_err))
val uid = tag!!.id
val sUID = Util.byteArrayToHexString(uid)
if (ctx.card.uid != sUID || cardProtocol != null) {
nfcManager.ignoreTag(isoDep.tag)
return
}
if (lastReadSuccess)
isoDep.timeout = 1000
else
isoDep.timeout = 65000
verifyCardTask = VerifyCardTask(ctx.card, NfcReader(nfcManager, isoDep), App.localStorage, App.pinStorage, App.firmwaresStorage, this)
verifyCardTask?.start()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun doShareWallet(useURI: Boolean) {
if (useURI) {
val engine = CoinEngineFactory.create(ctx)
val txtShare = engine!!.shareWalletUri.toString()
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_SUBJECT, "Wallet address")
intent.putExtra(Intent.EXTRA_TEXT, txtShare)
val packageManager = activity?.packageManager
val activities = packageManager?.queryIntentActivities(intent, PackageManager.MATCH_ALL)
val isIntentSafe = activities?.size!! > 0
if (isIntentSafe) {
// create intent to show chooser
val chooser = Intent.createChooser(intent, getString(R.string.share_wallet_address_with))
// verify the intent will resolve to at least one activity
if (intent.resolveActivity(activity!!.packageManager) != null) {
startActivity(chooser)
}
} else {
val clipboard = activity?.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare)
Toast.makeText(activity, R.string.copied_clipboard, Toast.LENGTH_LONG).show()
}
} else {
val txtShare = ctx.coinData.wallet
val clipboard = activity?.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare)
Toast.makeText(activity, R.string.copied_clipboard, Toast.LENGTH_LONG).show()
}
}
}

View file

@ -0,0 +1,493 @@
package com.tangem.ui.fragment
import android.app.Activity
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import android.os.Bundle
import androidx.core.content.ContextCompat
import android.text.Html
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.Toast
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.domain.wallet.CoinEngineFactory
import com.tangem.domain.wallet.TangemContext
import com.tangem.ui.activity.*
import com.tangem.ui.dialog.PINSwapWarningDialog
import com.tangem.ui.event.DeletingWalletFinish
import com.tangem.cardandroid.android.data.PINStorage
import com.tangem.cardandroid.android.nfc.NfcLifecycleObserver
import com.tangem.cardandroid.android.reader.NfcManager
import com.tangem.cardandroid.data.loadFromBundle
import com.tangem.cardcommon.data.TangemCard
import com.tangem.cardcommon.reader.CardProtocol
import com.tangem.util.LOG
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fr_verify_card.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.io.IOException
import java.util.*
class VerifyCard : androidx.fragment.app.Fragment(), NfcAdapter.ReaderCallback {
companion object {
val TAG: String = VerifyCard::class.java.simpleName
}
private lateinit var nfcManager: NfcManager
private lateinit var ctx: TangemContext
private var requestPIN2Count = 0
private var timerHideErrorAndMessage: Timer? = null
private var newPIN = ""
private var newPIN2 = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ctx = TangemContext.loadFromBundle(activity, activity?.intent?.extras)
nfcManager = NfcManager(activity!!, this)
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fr_verify_card, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateViews()
srlVerifyCard.setOnRefreshListener { srlVerifyCard.isRefreshing = false }
// set listeners
fabMenu.setOnClickListener { showMenu(fabMenu) }
btnOk.setOnClickListener {
val data = prepareResultIntent()
data.putExtra(Constant.EXTRA_MODIFICATION, "update")
activity?.finish()
}
}
override fun onStart() {
super.onStart()
if (!EventBus.getDefault().isRegistered(this))
EventBus.getDefault().register(this)
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Subscribe
fun onDeleteWalletFinish(deletingWalletFinish: DeletingWalletFinish) {
activity?.finish()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
LOG.i(TAG, "requestCode $requestCode resultCode $resultCode")
when (requestCode) {
Constant.REQUEST_CODE_ENTER_NEW_PIN -> if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (data.extras!!.containsKey("confirmPIN")) {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN = data.getStringExtra("newPIN")
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
} else {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("newPIN", data.getStringExtra("newPIN"))
intent.putExtra("mode", PinRequestActivity.Mode.ConfirmNewPIN.toString())
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN)
}
}
}
Constant.REQUEST_CODE_ENTER_NEW_PIN2 -> if (resultCode == Activity.RESULT_OK) {
if (data != null) {
if (data.extras!!.containsKey("confirmPIN2")) {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN2 = data.getStringExtra("newPIN2")
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
} else {
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("newPIN2", data.getStringExtra("newPIN2"))
intent.putExtra("mode", PinRequestActivity.Mode.ConfirmNewPIN2.toString())
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN2)
}
}
}
Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (newPIN == "") newPIN = ctx.card!!.pin
if (newPIN2 == "") newPIN2 = App.pinStorage.piN2
val pinSwapWarningDialog = PINSwapWarningDialog()
pinSwapWarningDialog.setOnRefreshPage { (activity as VerifyCardActivity).navigator.showPinSwap(context as Activity, newPIN, newPIN2) }
val bundle = Bundle()
if (!CardProtocol.isDefaultPIN(newPIN) || !CardProtocol.isDefaultPIN2(newPIN2))
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_forget))
else
bundle.putString(PINSwapWarningDialog.EXTRA_MESSAGE, getString(R.string.if_you_use_default))
pinSwapWarningDialog.arguments = bundle
activity?.supportFragmentManager?.let { pinSwapWarningDialog.show(it, PINSwapWarningDialog.TAG) }
}
Constant.REQUEST_CODE_SWAP_PIN -> if (resultCode == Activity.RESULT_OK) {
if (data == null) {
ctx.saveToIntent(data)
data?.putExtra("modification", "delete")
} else
data.putExtra("modification", "update")
activity!!.setResult(Activity.RESULT_OK, data)
activity!!.finish()
} else {
if (data != null && data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
ctx.error = data.getStringExtra("message")
}
}
}
Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE -> if (resultCode == Activity.RESULT_OK)
(activity as VerifyCardActivity).navigator.showPurge(context as Activity, ctx)
// Constant.REQUEST_CODE_PURGE -> if (resultCode == Activity.RESULT_OK) {
// if (data == null) {
// ctx.saveToIntent(data)
// data?.putExtra(Constant.EXTRA_MODIFICATION, "delete")
// } else
// data.putExtra(Constant.EXTRA_MODIFICATION, "update")
//
// activity?.setResult(Activity.RESULT_OK, data)
// activity?.finish()
// }
else {
if (data != null && data.extras!!.containsKey("UID") && data.extras!!.containsKey("Card")) {
val updatedCard = TangemCard(data.getStringExtra("UID"))
updatedCard.loadFromBundle(data.getBundleExtra("Card"))
ctx.card = updatedCard
}
if (resultCode == Constant.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
return
} else {
if (data != null && data.extras!!.containsKey("message")) {
ctx.error = data.getStringExtra("message")
}
}
updateViews()
}
}
}
override fun onTagDiscovered(tag: Tag?) {
try {
nfcManager.ignoreTag(tag!!)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun updateViews() {
try {
if (timerHideErrorAndMessage != null) {
timerHideErrorAndMessage!!.cancel()
timerHideErrorAndMessage = null
}
tvCardID.text = ctx.card!!.cidDescription
if (ctx.error == null || ctx.error.isEmpty()) {
tvError.visibility = View.GONE
tvError.text = ""
} else {
tvError.visibility = View.VISIBLE
tvError.text = ctx.error
}
if (ctx.message == null || ctx.message.isEmpty()) {
tvMessage.visibility = View.GONE
tvMessage.text = ""
} else {
tvMessage.visibility = View.VISIBLE
tvMessage.text = ctx.message
}
tvManufacturerInfo.text = ctx.card!!.manufacturer.officialName
if (ctx.card!!.isManufacturerConfirmed && ctx.card!!.isCardPublicKeyValid) {
tvCardIdentity.setText(R.string.attested)
tvCardIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed))
} else {
tvCardIdentity.setText(R.string.not_confirmed)
tvCardIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed))
}
tvIssuer.text = ctx.card!!.issuerDescription
tvCardRegistredDate.text = DateUtils.formatDateTime(null, ctx.card!!.personalizationDateTime.time, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NUMERIC_DATE or DateUtils.FORMAT_SHOW_YEAR)
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
else
Html.fromHtml(ctx.blockchainName)
tvBlockchain.text = html
tvValidationNode.text = ctx.coinData!!.validationNodeDescription
val engine = CoinEngineFactory.create(ctx)
tvInputs.text = engine!!.unspentInputsDescription
ivBlockchain.setImageResource(Blockchain.getLogoImageResource(ctx.card!!.blockchainID, ctx.card!!.tokenSymbol))
if (ctx.card!!.isReusable!!)
tvReusable.setText(R.string.reusable)
else
tvReusable.setText(R.string.one_off_banknote)
tvSigningMethod.text = ctx.card!!.signingMethod.description
if (ctx.card!!.status == TangemCard.Status.Loaded || ctx.card!!.status == TangemCard.Status.Purged) {
when {
ctx.card!!.remainingSignatures == 0 -> {
tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed))
tvRemainingSignatures.setText(R.string.none)
}
ctx.card!!.remainingSignatures == 1 -> {
tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed))
tvRemainingSignatures.setText(R.string.last_one)
}
ctx.card!!.remainingSignatures > 1000 -> {
tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed))
tvRemainingSignatures.setText(R.string.unlimited)
}
else -> {
tvRemainingSignatures.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed))
tvRemainingSignatures.text = ctx.card!!.remainingSignatures.toString()
}
}
tvSignedTx.text = (ctx.card!!.maxSignatures - ctx.card!!.remainingSignatures).toString()
} else {
tvLastSigned.text = ""
tvRemainingSignatures.text = ""
tvSignedTx.text = ""
}
tvFirmware.text = ctx.card!!.firmwareVersion
var features = ""
features += if (ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!!) {
"Allows change PIN1 and PIN2\n"
} else if (ctx.card!!.allowSwapPIN()!!) {
"Allows change PIN1\n"
} else if (ctx.card!!.allowSwapPIN2()!!) {
"Allows change PIN2\n"
} else {
"Fixed PIN1 and PIN2\n"
}
if (ctx.card!!.needCVC()!!)
features += "Requires CVC\n"
if (ctx.card!!.supportDynamicNDEF()!!) {
features += "Dynamic NDEF for iOS\n"
} else if (ctx.card!!.supportNDEF()!!)
features += "NDEF\n"
if (ctx.card!!.supportBlock()!!)
features += "Blockable\n"
if (ctx.card!!.supportOnlyOneCommandAtTime()!!)
features += "Atomic command mode"
if (features.endsWith("\n"))
features = features.substring(0, features.length - 1)
tvFeatures.text = features
if (ctx.card!!.useDefaultPIN1()) {
imgPIN.setImageResource(R.drawable.unlock_pin1)
imgPIN.setOnClickListener { Toast.makeText(context, R.string.this_banknote_protected_default_PIN1_code, Toast.LENGTH_LONG).show() }
} else {
imgPIN.setImageResource(R.drawable.lock_pin1)
imgPIN.setOnClickListener { Toast.makeText(context, R.string.this_banknote_protected_user_PIN1_code, Toast.LENGTH_LONG).show() }
}
if (ctx.card!!.pauseBeforePIN2 > 0 && (ctx.card!!.useDefaultPIN2()!! || !ctx.card!!.useSmartSecurityDelay())) {
imgPIN2orSecurityDelay.setImageResource(R.drawable.timer)
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, String.format(getString(R.string.this_banknote_will_enforce), ctx.card!!.pauseBeforePIN2 / 1000.0), Toast.LENGTH_LONG).show() }
} else if (ctx.card!!.useDefaultPIN2()!!) {
imgPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2)
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.this_banknote_protected_default_PIN2_code, Toast.LENGTH_LONG).show() }
} else {
imgPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2)
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.this_banknote_protected_user_PIN2_code, Toast.LENGTH_LONG).show() }
}
if (ctx.card!!.useDevelopersFirmware()!!) {
imgDeveloperVersion.setImageResource(R.drawable.ic_developer_version)
imgDeveloperVersion.visibility = View.VISIBLE
imgDeveloperVersion.setOnClickListener { Toast.makeText(context, R.string.unlocked_banknote_only_development_use, Toast.LENGTH_LONG).show() }
} else
imgDeveloperVersion.visibility = View.INVISIBLE
if (ctx.card!!.status == TangemCard.Status.Loaded) {
tvWallet.text = ctx.coinData!!.shortWalletString
if (ctx.card!!.isWalletPublicKeyValid) {
tvWalletIdentity.setText(R.string.possession_proved)
tvWalletIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.confirmed))
} else {
tvWalletIdentity.setText(R.string.possession_not_proved)
tvWalletIdentity.setTextColor(ContextCompat.getColor(context!!, R.color.not_confirmed))
}
} else {
tvWallet!!.setText(R.string.not_available)
tvWalletIdentity.setText(R.string.no_data_string)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun doSetPin() {
requestPIN2Count = 0
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestNewPIN.toString())
newPIN = ""
newPIN2 = ""
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN)
}
private fun doResetPin() {
requestPIN2Count = 0
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN = PINStorage.getDefaultPIN()
newPIN2 = ""
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doResetPin2() {
requestPIN2Count = 0
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN = ""
newPIN2 = PINStorage.getDefaultPIN2()
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doResetPins() {
requestPIN2Count = 0
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
newPIN = PINStorage.getDefaultPIN()
newPIN2 = PINStorage.getDefaultPIN2()
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_SWAP_PIN)
}
private fun doSetPin2() {
requestPIN2Count = 0
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestNewPIN2.toString())
newPIN = ""
newPIN2 = ""
startActivityForResult(intent, Constant.REQUEST_CODE_ENTER_NEW_PIN2)
}
private fun doPurge() {
requestPIN2Count = 0
val engine = CoinEngineFactory.create(ctx)
if (!engine!!.hasBalanceInfo()) {
return
} else if (engine.isBalanceNotZero) {
Toast.makeText(context, R.string.cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show()
return
}
val intent = Intent(context, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
ctx.saveToIntent(intent)
startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_FOR_PURGE)
}
fun prepareResultIntent(): Intent {
val data = Intent()
ctx.saveToIntent(data)
return data
}
private fun showMenu(v: View) {
val popup = PopupMenu(activity, v)
val inflater = popup.menuInflater
inflater.inflate(R.menu.menu_loaded_wallet, popup.menu)
popup.menu.findItem(R.id.action_set_PIN1).isVisible = ctx.card!!.allowSwapPIN()!!
popup.menu.findItem(R.id.action_reset_PIN1).isVisible = ctx.card!!.allowSwapPIN()!! && !ctx.card!!.useDefaultPIN1()
popup.menu.findItem(R.id.action_set_PIN2).isVisible = ctx.card!!.allowSwapPIN2()!!
popup.menu.findItem(R.id.action_reset_PIN2).isVisible = ctx.card!!.allowSwapPIN2()!! && !ctx.card!!.useDefaultPIN2()
popup.menu.findItem(R.id.action_reset_PINs).isVisible = ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!! && !ctx.card!!.useDefaultPIN1() && !ctx.card!!.useDefaultPIN2()
if (!ctx.card!!.isReusable || ctx.card!!.status != TangemCard.Status.Loaded)
popup.menu.findItem(R.id.action_purge).isVisible = false
popup.setOnMenuItemClickListener { item ->
val id = item.itemId
when (id) {
R.id.action_set_PIN1 -> doSetPin()
R.id.action_reset_PIN1 -> doResetPin()
R.id.action_set_PIN2 -> doSetPin2()
R.id.action_reset_PIN2 -> doResetPin2()
R.id.action_reset_PINs -> doResetPins()
R.id.action_purge -> doPurge()
}
false
}
if (BuildConfig.DEBUG) {
popup.menu.findItem(R.id.action_set_PIN2).isEnabled = true
popup.menu.findItem(R.id.action_reset_PIN2).isEnabled = true
}
popup.show()
}
}

View file

@ -0,0 +1,34 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tangem.ui.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.tangem.data.network.exception.Failure
/**
* Base ViewModel class with default Failure handling.
* @see ViewModel
* @see Failure
*/
abstract class BaseViewModel : ViewModel() {
var failure: MutableLiveData<Failure> = MutableLiveData()
protected fun handleFailure(failure: Failure) {
this.failure.value = failure
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.ui.viewmodel
import androidx.lifecycle.MutableLiveData
import com.tangem.data.network.exception.Failure
class LoadedWalletViewModel : BaseViewModel() {
private val state: MutableLiveData<State> = MutableLiveData()
fun getState() = state
enum class State {
ServerError,
Failed,
Success
}
fun connectToken(data: String, data2: String) {
}
private fun handleTokensSaveFailure(failure: Failure) {
state.value = State.Success
handleFailure(failure)
}
private fun handleLoginFailure(failure: Failure) {
when (failure) {
is Failure.ServerError -> state.value = State.ServerError
}
handleFailure(failure)
}
}