tangem-app-android-audited/app/src/main/java/com/tangem/ui/fragment/MainFragment.kt
2020-01-21 17:08:32 +03:00

455 lines
No EOL
18 KiB
Kotlin

package com.tangem.ui.fragment
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.net.Uri
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.nfc.tech.NfcV
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.view.*
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProviders
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
import com.tangem.data.Logger
import com.tangem.tangem_card.data.TangemCard
import com.tangem.tangem_card.reader.CardProtocol
import com.tangem.tangem_card.reader.TLV
import com.tangem.tangem_card.reader.TLVException
import com.tangem.tangem_card.reader.TLVList
import com.tangem.tangem_card.tasks.CustomReadCardTask
import com.tangem.tangem_card.tasks.ReadCardInfoTask
import com.tangem.tangem_sdk.android.data.PINStorage
import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
import com.tangem.tangem_sdk.android.reader.NfcReader
import com.tangem.tangem_sdk.android.reader.NfcVReader
import com.tangem.tangem_sdk.android.reader.ReadResult
import com.tangem.tangem_sdk.android.reader.ReadSlixTagTask
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
import com.tangem.tangem_sdk.data.loadFromBundle
import com.tangem.tangem_sdk.data.saveToBundle
import com.tangem.ui.activity.MainActivity
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.ui.fragment.pin.PinRequestFragment
import com.tangem.ui.navigation.NavigationResultListener
import com.tangem.util.CommonUtil
import com.tangem.util.LOG
import com.tangem.util.UtilHelper
import com.tangem.util.extensions.colorFrom
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.CoinEngineFactory
import com.tangem.wallet.R
import com.tangem.wallet.TangemContext
import com.tangem.wallet.xlmTag.XlmTagEngine
import kotlinx.android.synthetic.main.fragment_main.*
import kotlinx.android.synthetic.main.layout_touch_card.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.io.File
import java.util.*
import kotlin.coroutines.CoroutineContext
class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback,
CardProtocol.Notifications, androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener,
PopupMenu.OnMenuItemClickListener {
companion object {
fun newInstance() = MainFragment()
val TAG: String = MainFragment::class.java.simpleName
}
override val layoutId = R.layout.fragment_main
private lateinit var viewModel: MainViewModel
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
private var unsuccessReadCount = 0
private var task: CustomReadCardTask? = null
private var lastTag: Tag? = null
private var zipFile: File? = null
private var unknownBlockchain = false
private val parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.IO
private val scope = CoroutineScope(coroutineContext)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
setHasOptionsMenu(true)
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rippleBackgroundNfc.startRippleAnimation()
// init NFC Antenna
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
nfcDeviceAntenna.init()
// set phone name
if (nfcDeviceAntenna.fullName != "")
tvNFCHint.text = String.format(getString(R.string.main_screen_scan_card), nfcDeviceAntenna.fullName)
else
tvNFCHint.text = String.format(getString(R.string.main_screen_scan_card), getString(R.string.main_screen_phone))
// set listeners
// ivMenu.setOnClickListener { showMenu(it) }
val toolbar = tb_main
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
(activity as? AppCompatActivity)?.supportActionBar?.setDisplayShowTitleEnabled(false)
setCardStoreLink()
}
private fun setCardStoreLink() {
val text = getString(R.string.main_screen_visit_store, getString(R.string.main_screen_store_address))
val spannable = SpannableString(text)
spannable.setSpan(
ForegroundColorSpan(requireContext().colorFrom(R.color.colorAccent)),
text.indexOf(getString(R.string.main_screen_store_address)),
text.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
tvBuyCards?.setText(spannable, TextView.BufferType.SPANNABLE)
llShoppingView?.setOnClickListener {
val uri = Uri.parse("https://www.tangemcards.com")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
//
// // show snackbar about new version app
// viewModel.getVersionName().observe(this, Observer { text ->
// (activity as MainActivity).toastHelper.showSnackbarUpdateVersion(requireContext(), cl, text)
// })
val intent = activity?.intent
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action)) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
onTagDiscovered(tag)
}
}
override fun onResume() {
super.onResume()
nfcDeviceAntenna.animate()
}
override fun onPause() {
task?.cancel(true)
super.onPause()
}
override fun onStop() {
task?.cancel(true)
super.onStop()
}
override fun onTagDiscovered(tag: Tag) {
if (unknownBlockchain) {
(activity as MainActivity).nfcManager.ignoreTag(tag)
return
}
NfcV.get(tag)?.let { onNfcVDiscovered(it, tag.id) }
try {
// get IsoDep handle and run cardReader thread
val isoDep = IsoDep.get(tag)
if (unsuccessReadCount < 2)
isoDep.timeout = 2000 + 5000 * unsuccessReadCount
else
isoDep.timeout = 90000
lastTag = tag
val terminalKeys = viewModel.getTerminalKeys()
PINStorage.setTerminalPrivateKey(terminalKeys[Constant.TERMINAL_PRIVATE_KEY])
PINStorage.setTerminalPublicKey(terminalKeys[Constant.TERMINAL_PUBLIC_KEY])
task = ReadCardInfoTask(NfcReader((activity as MainActivity).nfcManager, isoDep),
App.localStorage, App.pinStorage, this)
task?.start()
} catch (e: Exception) {
e.printStackTrace()
(activity as MainActivity).nfcManager.notifyReadResult(false)
}
}
private fun onNfcVDiscovered(nfcV: NfcV, uid: ByteArray) {
scope.launch {
when (val readResult = ReadSlixTagTask(NfcVReader(nfcV)).read()) {
is ReadResult.Failure -> (activity as MainActivity).nfcManager.notifyReadResult(false)
is ReadResult.Success -> {
try {
val tlvs = readResult.tlvs
val cardDataTlv = TLVList.fromBytes((tlvs.getTLV(TLV.Tag.TAG_CardData)).Value)
Log.v(TAG, "\n" + tlvs.getParsedTLVs(""))
val card = TangemCard(uid.toString())
card.batch = cardDataTlv.getTLV(TLV.Tag.TAG_Batch).asHexString
card.setIssuer(cardDataTlv.getTLV(TLV.Tag.TAG_Issuer_ID).Value.toString(), null)
card.blockchainID = Blockchain.StellarTag.id
card.walletPublicKey = tlvs.getTLV(TLV.Tag.TAG_Wallet_PublicKey).Value
card.status = TangemCard.Status.Loaded
card.tagSignature = tlvs.getTLV(TLV.Tag.TAG_Signature).Value
val ctx = TangemContext(card)
val engineCoin = XlmTagEngine(ctx)
engineCoin.defineWallet()
launch(Dispatchers.Main) {
val bundle = Bundle()
bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
ctx.saveToBundle(bundle)
navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
R.id.action_main_to_tagFragment, bundle)
}
} catch (e: TLVException) {
e.printStackTrace()
Log.v(TAG, e.message)
}
}
}
}
}
override fun onReadStart(cardProtocol: CardProtocol) {
rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE }
}
override fun onReadProgress(protocol: CardProtocol, progress: Int) {
}
override fun onReadFinish(cardProtocol: CardProtocol?) {
task = null
if (cardProtocol != null) {
if (cardProtocol.error == null) {
(activity as MainActivity).nfcManager.notifyReadResult(true)
rlProgressBar?.post {
rlProgressBar?.visibility = View.GONE
// TODO - ??? remove save and load???
val cardInfo = Bundle()
val bCard = Bundle()
cardInfo.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
cardInfo.putBundle(EXTRA_TANGEM_CARD, bCard)
cardProtocol.card.saveToBundle(bCard)
val uid = cardInfo.getString(EXTRA_TANGEM_CARD_UID)
val card = TangemCard(uid)
cardInfo.getBundle(EXTRA_TANGEM_CARD)?.let { card.loadFromBundle(it) }
val terminalKeys = viewModel.getTerminalKeys()
card.terminalPrivateKey = terminalKeys[Constant.TERMINAL_PRIVATE_KEY]
card.terminalPublicKey = terminalKeys[Constant.TERMINAL_PUBLIC_KEY]
val ctx = TangemContext(card)
when {
card.status == TangemCard.Status.Loaded -> lastTag?.let {
val engineCoin = CoinEngineFactory.create(ctx)
if (engineCoin != null) {
engineCoin.defineWallet()
val bundle = Bundle()
bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
ctx.saveToBundle(bundle)
navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
R.id.action_main_to_loadedWalletFragment, bundle)
} else {
showUnkownBlockchainWarning()
}
}
card.status == TangemCard.Status.Empty -> {
val engineCoin = CoinEngineFactory.create(ctx)
if (engineCoin != null) {
val bundle = Bundle().apply { ctx.saveToBundle(this) }
navigateToDestination(R.id.action_main_to_emptyWalletFragment, bundle)
} else {
showUnkownBlockchainWarning()
}
}
card.status == TangemCard.Status.Purged -> Toast.makeText(context, R.string.main_screen_erased_wallet, Toast.LENGTH_SHORT).show()
card.status == TangemCard.Status.NotPersonalized -> Toast.makeText(context, R.string.main_screen_not_personalized, Toast.LENGTH_SHORT).show()
else -> {
// val bundle = Bundle()
// bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
// ctx.saveToBundle(bundle)
// navController.navigate(R.id.loadedWallet, bundle)
// lastTag?.let { navigator.showLoadedWallet(this, it, ctx) }
}
}
}
} else {
// remove last UIDs because of error and no card read
rlProgressBar.post {
context?.let { Toast.makeText(it, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() }
unsuccessReadCount++
if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) {
val data = Bundle().apply { putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN.toString()) }
navigateForResult(Constant.REQUEST_CODE_ENTER_PIN_ACTIVITY,
R.id.action_main_to_pinRequestFragment, data)
} else {
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported)
if (!NoExtendedLengthSupportDialog.allReadyShowed)
NoExtendedLengthSupportDialog().show(activity!!.supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
lastTag = null
ReadCardInfoTask.resetLastReadInfo()
(activity as MainActivity).nfcManager.notifyReadResult(false)
}
}
}
}
rlProgressBar?.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500)
}
private fun showUnkownBlockchainWarning() {
unknownBlockchain = true
AlertDialog.Builder(context)
.setTitle(R.string.dialog_warning)
.setMessage(R.string.alert_unknown_blockchain)
.setPositiveButton(R.string.general_ok) { _, _ -> }
.setOnDismissListener { unknownBlockchain = false }
.create()
.show()
}
override fun onReadCancel() {
task = null
ReadCardInfoTask.resetLastReadInfo()
rlProgressBar?.postDelayed({ rlProgressBar?.visibility = View.GONE }, 500)
}
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
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()
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main, menu)
if (BuildConfig.DEBUG) {
for (i in 0 until menu.size()) menu.getItem(i).isVisible = true
}
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.sendLogs -> {
var f: File? = null
try {
f = Logger.collectLogs(activity)
if (f != null) {
LOG.e(TAG, String.format("Collect %d log bytes", f.length()))
CommonUtil.sendEmail(activity, zipFile, TAG, "Logs", UtilHelper.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 -> {
navigateToDestination(R.id.action_main_to_pinSaveFragment,
bundleOf(Constant.EXTRA_PIN2 to false))
return true
}
R.id.managePIN2 -> {
navigateToDestination(R.id.action_main_to_pinSaveFragment,
bundleOf(Constant.EXTRA_PIN2 to true))
return true
}
R.id.settings -> {
navigateToDestination(R.id.action_main_to_settingsFragment)
return true
}
R.id.about -> {
val bundle = Bundle().apply { putBoolean(Constant.EXTRA_AUTO_HIDE, false) }
navigateToDestination(R.id.action_main_to_logoFragment, bundle)
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onReadWait(msec: Int) {
WaitSecurityDelayDialog.onReadWait(Objects.requireNonNull(activity) as AppCompatActivity?, msec)
}
override fun onReadBeforeRequest(timeout: Int) {
WaitSecurityDelayDialog.onReadBeforeRequest(Objects.requireNonNull(activity) as AppCompatActivity?, timeout)
}
override fun onReadAfterRequest() {
WaitSecurityDelayDialog.onReadAfterRequest(Objects.requireNonNull(activity))
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
return onOptionsItemSelected(item!!)
}
private fun showMenu(v: View) {
val popup = PopupMenu(context, 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()
}
}