tangem-app-android-audited/app/src/main/java/com/tangem/presentation/activity/ConfirmPaymentActivity.kt
2018-09-11 11:17:10 +03:00

474 lines
No EOL
18 KiB
Kotlin

package com.tangem.presentation.activity
import android.app.Activity
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.text.Editable
import android.text.Html
import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.*
import com.tangem.data.network.ServerApiHelper
import com.tangem.data.network.model.InfuraResponse
import com.tangem.data.network.request.ElectrumRequest
import com.tangem.data.network.request.FeeRequest
import com.tangem.data.network.task.confirm_payment.ConnectFeeTask
import com.tangem.data.network.task.confirm_payment.ConnectTask
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.util.BTCUtils
import com.tangem.util.DerEncodingUtil
import com.tangem.util.FormatUtil
import com.tangem.util.Util
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.activity_confirm_payment.*
import java.io.IOException
import java.math.BigInteger
import java.util.*
class ConfirmPaymentActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
companion object {
private const val REQUEST_CODE_SIGN_PAYMENT = 1
private const val REQUEST_CODE_REQUEST_PIN2 = 2
}
private var nfcManager: NfcManager? = null
private var serverApiHelper: ServerApiHelper? = null
var card: TangemCard? = null
var feeRequestSuccess = false
var balanceRequestSuccess = false
var etAmount: EditText? = null
var etFee: EditText? = null
var rgFee: RadioGroup? = null
var progressBar: ProgressBar? = null
var btnSend: Button? = null
var minFee: String? = null
var maxFee: String? = null
var normalFee: String? = null
var incFee: Boolean = true
var minFeeInInternalUnits: Long? = 0L
private var requestPIN2Count = 0
var nodeCheck = false
var dtVerified: Date? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_confirm_payment)
MainActivity.commonInit(applicationContext)
nfcManager = NfcManager(this, this)
serverApiHelper = ServerApiHelper()
card = TangemCard(intent.getStringExtra("UID"))
card!!.loadFromBundle(intent.extras!!.getBundle("Card"))
progressBar = findViewById(R.id.progressBar)
btnSend = findViewById(R.id.btnSend)
etAmount = findViewById(R.id.etAmount)
etFee = findViewById(R.id.etFee)
rgFee = findViewById(R.id.rgFee)
val engine = CoinEngineFactory.create(card!!.blockchain)
if (card!!.blockchain == Blockchain.Token) {
val html = Html.fromHtml(engine!!.getBalanceWithAlter(card))
tvBalance.text = html
} else
tvBalance.text = engine!!.getBalanceWithAlter(card)
incFee = intent.getBooleanExtra("IncFee", true);
if (incFee) {
tvIncFee!!.setText(R.string.including_fee)
} else {
tvIncFee!!.setText(R.string.not_including_fee)
}
if (card!!.blockchain == Blockchain.Token) {
tvIncFee!!.visibility = View.INVISIBLE
} else {
tvIncFee!!.visibility = View.VISIBLE
}
etAmount!!.setText(intent.getStringExtra(SignPaymentActivity.EXTRA_AMOUNT))
tvCurrency.text = engine.getBalanceCurrency(card)
tvCurrency2.text = engine.feeCurrency
tvCardID.text = card!!.cidDescription
etWallet!!.setText(intent.getStringExtra("Wallet"))
etFee!!.setText("?")
btnSend!!.visibility = View.INVISIBLE
feeRequestSuccess = false
balanceRequestSuccess = false
if (card!!.blockchain == Blockchain.Ethereum || card!!.blockchain == Blockchain.EthereumTestNet || card!!.blockchain == Blockchain.Token) {
rgFee!!.isEnabled = false
requestInfura(ServerApiHelper.INFURA_ETH_GAS_PRICE)
} else {
rgFee!!.isEnabled = true
val data = SharedData(SharedData.COUNT_REQUEST)
val engineCoin = CoinEngineFactory.create(card!!.blockchain)
for (i in 0 until data.allRequest) {
val nodeAddress = engineCoin!!.getNextNode(card)
val nodePort = engineCoin.getNextNodePort(card)
val connectTaskEx = ConnectTask(this@ConfirmPaymentActivity, nodeAddress, nodePort, data)
connectTaskEx.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ElectrumRequest.checkBalance(card!!.wallet))
}
var calcSize = 256
try {
calcSize = buildSize(etWallet!!.text.toString(), "0.00", etAmount!!.text.toString())
} catch (ex: Exception) {
Log.e("Build Fee error", ex.message)
}
card!!.resetFailedBalanceRequestCounter()
val sharedFee = SharedData(SharedData.COUNT_REQUEST)
progressBar!!.visibility = View.VISIBLE
// serverApiHelper!!.estimateFee(ServerApiHelper.ESTIMATE_FEE_PRIORITY)
// serverApiHelper!!.estimateFee(ServerApiHelper.ESTIMATE_FEE_NORMAL)
// serverApiHelper!!.estimateFee(ServerApiHelper.ESTIMATE_FEE_MINIMAL)
for (i in 0 until SharedData.COUNT_REQUEST) {
val feeTask = ConnectFeeTask(this@ConfirmPaymentActivity, sharedFee)
feeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
FeeRequest.GetFee(card!!.wallet, calcSize.toLong(), FeeRequest.NORMAL),
FeeRequest.GetFee(card!!.wallet, calcSize.toLong(), FeeRequest.MINIMAL),
FeeRequest.GetFee(card!!.wallet, calcSize.toLong(), FeeRequest.PRIORITY))
}
}
// 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 engine = CoinEngineFactory.create(card!!.blockchain)
val eqFee = engine!!.evaluateFeeEquivalent(card, etFee!!.text.toString())
tvFeeEquivalent.text = eqFee
if (!card!!.amountEquivalentDescriptionAvailable) {
tvFeeEquivalent.error = "Service unavailable"
tvCurrency2.visibility = View.GONE
tvFeeEquivalent.visibility = View.GONE
} else
tvFeeEquivalent.error = null
} catch (e: Exception) {
e.printStackTrace()
tvFeeEquivalent.text = ""
tvCurrency2.visibility = View.GONE
tvFeeEquivalent.visibility = View.GONE
}
}
override fun afterTextChanged(s: Editable) {
}
})
btnSend!!.setOnClickListener {
val calendar = Calendar.getInstance()
calendar.add(Calendar.MINUTE, -1)
if (dtVerified == null || dtVerified!!.before(calendar.time)) {
finishActivityWithError(Activity.RESULT_CANCELED, getString(R.string.the_obtained_data_is_outdated_try_again))
return@setOnClickListener
}
val engineCoin = CoinEngineFactory.create(card!!.blockchain)
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 = etFee!!.text.toString()
val txAmount = etAmount!!.text.toString()
if (!engineCoin.hasBalanceInfo(card)) {
finishActivityWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
return@setOnClickListener
} else if (!engineCoin.isBalanceNotZero(card)) {
finishActivityWithError(Activity.RESULT_CANCELED, getString(R.string.the_wallet_is_empty))
return@setOnClickListener
} else if (!engineCoin.checkUnspentTransaction(card)) {
finishActivityWithError(Activity.RESULT_CANCELED, getString(R.string.please_wait_for_confirmation_of_incoming_transaction))
return@setOnClickListener
}
if (!engineCoin.checkAmountValue(card, txAmount, txFee, minFeeInInternalUnits, incFee)) {
finishActivityWithError(Activity.RESULT_CANCELED, getString(R.string.not_enough_funds_or_incorrect_amount))
return@setOnClickListener
}
requestPIN2Count = 0
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", card!!.uid)
intent.putExtra("Card", card!!.asBundle)
intent.putExtra("IncFee", incFee)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
}
// request estimate fee listener
serverApiHelper!!.setEstimateFee { blockCount, estimateFeeResponse ->
when (blockCount) {
ServerApiHelper.ESTIMATE_FEE_PRIORITY -> {
// Log.i("estimate_fee_PRIORITY", estimateFeeResponse)
}
ServerApiHelper.ESTIMATE_FEE_NORMAL -> {
// Log.i("estimate_fee_NORMAL", estimateFeeResponse)
}
ServerApiHelper.ESTIMATE_FEE_MINIMAL -> {
// Log.i("estimate_fee_MINIMAL", estimateFeeResponse)
}
}
}
// request eth gasPrice listener
val infuraBodyListener: ServerApiHelper.InfuraBodyListener = object : ServerApiHelper.InfuraBodyListener {
override fun onInfuraSuccess(method: String, infuraResponse: InfuraResponse) {
when (method) {
ServerApiHelper.INFURA_ETH_GAS_PRICE -> {
var gasPrice = infuraResponse.result
gasPrice = gasPrice.substring(2)
var l = BigInteger(gasPrice, 16)
val m = if (card!!.blockchain == Blockchain.Token) BigInteger.valueOf(60000) else BigInteger.valueOf(21000)
l = l.multiply(m)
val minFeeInGwei = card!!.getAmountInGwei(l.toString())
val normalFeeInGwei = card!!.getAmountInGwei(l.multiply(BigInteger.valueOf(12)).divide(BigInteger.valueOf(10)).toString())
val maxFeeInGwei = card!!.getAmountInGwei(l.multiply(BigInteger.valueOf(15)).divide(BigInteger.valueOf(10)).toString())
minFee = minFeeInGwei
normalFee = normalFeeInGwei
maxFee = maxFeeInGwei
etFee!!.setText(normalFeeInGwei)
etFee!!.error = null
btnSend!!.visibility = View.VISIBLE
feeRequestSuccess = true
balanceRequestSuccess = true
dtVerified = Date()
minFeeInInternalUnits = card!!.internalUnitsFromString(normalFeeInGwei)
Log.i("eth_gas_price", gasPrice)
}
else -> {
}
}
}
override fun onInfuraFail(method: String, message: String) {
when (method) {
ServerApiHelper.INFURA_ETH_GAS_PRICE -> {
}
else -> {
}
}
}
}
serverApiHelper!!.setInfuraResponse(infuraBodyListener)
}
private fun requestInfura(method: String) {
serverApiHelper!!.infura(method, 67, card!!.wallet, "", "")
}
public override fun onResume() {
super.onResume()
nfcManager!!.onResume()
}
public override fun onPause() {
super.onPause()
nfcManager!!.onPause()
}
public override fun onStop() {
super.onStop()
nfcManager!!.onStop()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SIGN_PAYMENT) {
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"))
card = updatedCard
}
}
if (resultCode == SignPaymentActivity.RESULT_INVALID_PIN && requestPIN2Count < 2) {
requestPIN2Count++
val intent = Intent(baseContext, PinRequestActivity::class.java)
intent.putExtra("mode", PinRequestActivity.Mode.RequestPIN2.toString())
intent.putExtra("UID", card!!.uid)
intent.putExtra("Card", card!!.asBundle)
intent.putExtra("IncFee", incFee)
startActivityForResult(intent, REQUEST_CODE_REQUEST_PIN2)
return
}
setResult(resultCode, data)
finish()
} else if (requestCode == REQUEST_CODE_REQUEST_PIN2) {
if (resultCode == Activity.RESULT_OK) {
val intent = Intent(baseContext, SignPaymentActivity::class.java)
intent.putExtra("UID", card!!.uid)
intent.putExtra("Card", card!!.asBundle)
intent.putExtra("Wallet", etWallet!!.text.toString())
intent.putExtra(SignPaymentActivity.EXTRA_AMOUNT, etAmount!!.text.toString())
intent.putExtra("Fee", etFee!!.text.toString())
intent.putExtra("IncFee", incFee)
startActivityForResult(intent, REQUEST_CODE_SIGN_PAYMENT)
} else
Toast.makeText(baseContext, R.string.pin_2_is_required_to_sign_the_payment, 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)
}
fun finishActivityWithError(errorCode: Int, message: String) {
val intent = Intent()
intent.putExtra("message", message)
setResult(errorCode, intent)
finish()
}
override fun onTagDiscovered(tag: Tag) {
try {
// Log.w(getClass().getName(), "Ignore discovered tag!");
nfcManager!!.ignoreTag(tag)
} catch (e: IOException) {
e.printStackTrace()
}
}
@Throws(Exception::class)
internal fun buildSize(outputAddress: String, outFee: String, outAmount: String): Int {
val myAddress = card!!.wallet
val pbKey = card!!.walletPublicKey
val pbComprKey = card!!.walletPublicKeyRar
// build script for our address
val rawTxList = card!!.unspentTransactions
val outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes
// collect unspent
val unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend)
var fullAmount: Long = 0
for (i in unspentOutputs.indices) {
fullAmount += unspentOutputs[i].value
}
// Get first unspent
val outPut = unspentOutputs[0]
val outPutIndex = outPut.outputIndex
// get prev TX id;
val prevTXID = rawTxList[0].txID//"f67b838d6e2c0c587f476f583843e93ff20368eaf96a798bdc25e01f53f8f5d2";
val fees = FormatUtil.ConvertStringToLong(outFee)
var amount = FormatUtil.ConvertStringToLong(outAmount)
amount = amount - fees
val change = fullAmount - fees - amount
if (amount + fees > fullAmount) {
throw Exception(String.format("Balance (%d) < amount (%d) + (%d)", fullAmount, change, amount))
}
val hashesForSign = arrayOfNulls<ByteArray>(unspentOutputs.size)
for (i in unspentOutputs.indices) {
val newTX = BTCUtils.buildTXForSign(myAddress, outputAddress, myAddress, unspentOutputs, i, amount, change)
val hashData = Util.calculateSHA256(newTX)
val doubleHashData = Util.calculateSHA256(hashData)
// Log.e("TX_BODY_1", BTCUtils.toHex(newTX));
// Log.e("TX_HASH_1", BTCUtils.toHex(hashData));
// Log.e("TX_HASH_2", BTCUtils.toHex(doubleHashData));
unspentOutputs[i].bodyDoubleHash = doubleHashData
unspentOutputs[i].bodyHash = hashData
hashesForSign[i] = doubleHashData
}
val signFromCard = ByteArray(64 * unspentOutputs.size)
for (i in unspentOutputs.indices) {
val r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0 + i * 64, 32 + i * 64))
val s = BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64))
val encodingSign = DerEncodingUtil.packSignDer(r, s, pbKey)
unspentOutputs[i].scriptForBuild = encodingSign
}
val realTX = BTCUtils.buildTXForSend(outputAddress, myAddress, unspentOutputs, amount, change)
return realTX.size
}
fun doSetFee(checkedRadioButtonId: Int) {
when (checkedRadioButtonId) {
R.id.rbMinimalFee ->
if (minFee != null)
etFee!!.setText(minFee)
else
etFee!!.setText("?")
R.id.rbNormalFee ->
if (normalFee != null)
etFee!!.setText(normalFee)
else
etFee!!.setText("?")
R.id.rbMaximumFee ->
if (maxFee != null)
etFee!!.setText(maxFee)
else
etFee!!.setText("?")
}
}
}