Updated on 2026-08-14
This commit is contained in:
parent
ce64b6827d
commit
f84e0d0a7c
7 changed files with 213 additions and 63 deletions
|
|
@ -35,6 +35,9 @@ 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
|
||||
|
|
@ -127,7 +130,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
|
||||
tvBuyCards?.setText(spannable, TextView.BufferType.SPANNABLE)
|
||||
llShoppingView?.setOnClickListener {
|
||||
val uri = Uri.parse ("https://www.tangemcards.com")
|
||||
val uri = Uri.parse("https://www.tangemcards.com")
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
|
@ -170,7 +173,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
return
|
||||
}
|
||||
|
||||
parseNfcvTag(tag)
|
||||
NfcV.get(tag)?.let { onNfcVDiscovered(it, tag.id) }
|
||||
|
||||
try {
|
||||
// get IsoDep handle and run cardReader thread
|
||||
|
|
@ -195,74 +198,41 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseNfcvTag(tag: Tag) {
|
||||
if (NfcV.get(tag) != null) {
|
||||
if (Ndef.get(tag) != null) {
|
||||
try {
|
||||
onNdefDiscovered(Ndef.get(tag), tag.id)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNdefDiscovered(ndef: Ndef, uid: ByteArray) {
|
||||
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) {
|
||||
|
||||
try {
|
||||
ndef.connect()
|
||||
val records = ndef.ndefMessage.records
|
||||
for (record in records) {
|
||||
if (record.toUri() != null) {
|
||||
when (record.toUri().toString()) {
|
||||
"vnd.android.nfc://ext/tangem.com:wallet" -> {
|
||||
//mConsole.write(Util.bytesToHex(record.getPayload()), MessageAdapter.MSG_OKAY, "", null, false);
|
||||
val payload = record.payload
|
||||
Log.v(TAG, "tangem.com:wallet[${payload.size} bytes]:")
|
||||
try {
|
||||
val tlvNDEF: TLVList = TLVList.fromBytes(Arrays.copyOfRange(payload, 2, payload.size))
|
||||
val cardDataTlv = TLVList.fromBytes((tlvNDEF.getTLV(TLV.Tag.TAG_CardData)).Value)
|
||||
Log.v(TAG, "\n" + tlvNDEF.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 = tlvNDEF.getTLV(TLV.Tag.TAG_Wallet_PublicKey).Value
|
||||
card.status = TangemCard.Status.Loaded
|
||||
card.tagSignature = tlvNDEF.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)
|
||||
}
|
||||
}
|
||||
else -> Log.v(TAG, record.toUri().toString())
|
||||
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)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ public interface NfcReader {
|
|||
|
||||
void connect();
|
||||
|
||||
boolean isConnected();
|
||||
}
|
||||
|
|
@ -69,4 +69,20 @@ public class TLVList extends ArrayList<TLV> {
|
|||
while (tlv != null);
|
||||
return tlvList;
|
||||
}
|
||||
|
||||
public static TLVList tryFromBytes(byte[] mData) {
|
||||
TLVList tlvList = new TLVList();
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(mData);
|
||||
TLV tlv = null;
|
||||
do {
|
||||
try {
|
||||
tlv = TLV.ReadFromStream(stream);
|
||||
if (tlv != null) tlvList.add(tlv);
|
||||
} catch (IOException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (tlv != null);
|
||||
return tlvList;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class NfcManager(private val activity: FragmentActivity, private val readerCallb
|
|||
|
||||
// reader mode flags: listen for type A (not B), skipping ndef check
|
||||
private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_V or
|
||||
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
|
||||
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
|
||||
private const val DELAY_PRESENCE = 1500
|
||||
|
||||
private const val REQUEST_NFC_PERMISSIONS = 1
|
||||
|
|
|
|||
|
|
@ -38,4 +38,8 @@ data class NfcReader(
|
|||
isoDep.timeout = timeout
|
||||
}
|
||||
|
||||
override fun isConnected(): Boolean {
|
||||
return isoDep.isConnected
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tangem_sdk.android.reader
|
||||
|
||||
import android.nfc.tech.NfcV
|
||||
|
||||
data class NfcVReader(
|
||||
var nfcV: NfcV?
|
||||
) : com.tangem.tangem_card.reader.NfcReader {
|
||||
|
||||
override fun getId(): ByteArray? {
|
||||
return nfcV?.tag?.id
|
||||
}
|
||||
|
||||
override fun setTimeout(timeout: Int) {
|
||||
}
|
||||
|
||||
override fun getTimeout(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun transceive(data: ByteArray?): ByteArray? {
|
||||
return nfcV?.transceive(data)
|
||||
}
|
||||
|
||||
override fun ignoreTag() {
|
||||
nfcV?.close()
|
||||
nfcV = null
|
||||
}
|
||||
|
||||
override fun notifyReadResult(success: Boolean) {
|
||||
}
|
||||
|
||||
override fun connect() {
|
||||
nfcV?.connect()
|
||||
}
|
||||
|
||||
override fun isConnected(): Boolean {
|
||||
return nfcV?.isConnected == true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package com.tangem.tangem_sdk.android.reader
|
||||
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NdefRecord
|
||||
import com.tangem.tangem_card.reader.NfcReader
|
||||
import com.tangem.tangem_card.reader.TLV
|
||||
import com.tangem.tangem_card.reader.TLVList
|
||||
import com.tangem.tangem_card.util.Util
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class ReadSlixTagTask(val nfcReader: NfcReader) {
|
||||
|
||||
private var lastErrorCode: Int = 0
|
||||
|
||||
fun read(): ReadResult {
|
||||
if (!nfcReader.isConnected) {
|
||||
try {
|
||||
nfcReader.connect()
|
||||
} catch (e: Exception) {
|
||||
return ReadResult.Failure(e)
|
||||
}
|
||||
}
|
||||
return try {
|
||||
runRead()
|
||||
} catch (e: Exception) {
|
||||
nfcReader.ignoreTag()
|
||||
ReadResult.Failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runRead(): ReadResult {
|
||||
val ndefMessage = runReadNDEF()
|
||||
val records: Array<NdefRecord> = ndefMessage.records
|
||||
for (record in records) {
|
||||
if (record.toUri() != null && record.toUri().toString() == "vnd.android.nfc://ext/tangem.com:wallet") {
|
||||
val payload = record.payload
|
||||
val tlvNDEF = TLVList.fromBytes(Arrays.copyOfRange(payload, 2, payload.size))
|
||||
nfcReader.ignoreTag()
|
||||
return ReadResult.Success(tlvNDEF)
|
||||
}
|
||||
}
|
||||
return ReadResult.Failure(Exception("No parcelable Tlv has been found."))
|
||||
}
|
||||
|
||||
private fun runReadNDEF(): NdefMessage {
|
||||
val answerCC = readSingleBlock(0x00)
|
||||
|
||||
if (answerCC.size != 4 || answerCC[0].toInt() != 0xE1 || ((answerCC[1].toInt() and 0xF0) != 0x40)) {
|
||||
|
||||
} else {
|
||||
throw Exception("Failed! Invalid CC read " + Util.bytesToHex(answerCC))
|
||||
}
|
||||
if ((answerCC[3].toInt() and 0x01) != 0x01) {
|
||||
throw Exception("Multiple block read unsupported!")
|
||||
}
|
||||
val areaSize = 8 * answerCC[2]
|
||||
val blocksCount = areaSize / 4
|
||||
|
||||
val areaBuf = readMultipleBlocks(1, blocksCount)
|
||||
|
||||
val tlvNDEF = TLVList.tryFromBytes(areaBuf)
|
||||
|
||||
return NdefMessage(tlvNDEF.getTLV(TLV.Tag.TAG_CardPublicKey).Value)
|
||||
}
|
||||
|
||||
private fun readSingleBlock(blockNo: Int): ByteArray {
|
||||
return doTransceive(0x20, blockNo)!!
|
||||
}
|
||||
|
||||
private fun readMultipleBlocks(startBlock: Int, blocksCount: Int): ByteArray {
|
||||
val resultBuf = ByteArrayOutputStream()
|
||||
val maxBlocksAtOnce = 32
|
||||
var blocksRemaining = blocksCount
|
||||
var firstBlockToRead = startBlock
|
||||
while (blocksRemaining > 0) {
|
||||
val blocksToRead = if (blocksRemaining > maxBlocksAtOnce) {
|
||||
maxBlocksAtOnce
|
||||
} else {
|
||||
blocksRemaining
|
||||
}
|
||||
val blocks = doTransceive(0x23.toByte(), firstBlockToRead, blocksToRead - 1)
|
||||
blocksRemaining -= blocksToRead
|
||||
firstBlockToRead += blocksToRead
|
||||
resultBuf.write(blocks!!)
|
||||
}
|
||||
return resultBuf.toByteArray()
|
||||
}
|
||||
|
||||
private fun doTransceive(cmd: Byte, p1: Int?, p2: Int? = null, params: ByteArray? = null): ByteArray? {
|
||||
val command: ByteArray
|
||||
val res: ByteArray?
|
||||
lastErrorCode = -1
|
||||
val os = ByteArrayOutputStream()
|
||||
os.write(REQ_FLAG.toInt())
|
||||
os.write(cmd.toInt())
|
||||
p1?.let { os.write(p1) }
|
||||
p2?.let { os.write(it) }
|
||||
params?.let { os.write(params, 0, params.size) }
|
||||
command = os.toByteArray()
|
||||
if (!nfcReader.isConnected) throw IOException("Connection lost")
|
||||
res = nfcReader.transceive(command)
|
||||
lastErrorCode = res[0].toInt()
|
||||
if (lastErrorCode != 0) {
|
||||
throw IOException("Error! Code: " + String.format("0x%02x", lastErrorCode))
|
||||
}
|
||||
return res.copyOfRange(1, res.size)
|
||||
}
|
||||
|
||||
companion object{
|
||||
// iso15693 flags
|
||||
private const val REQ_FLAG: Byte = 0x02
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ReadResult {
|
||||
data class Success(val tlvs: TLVList) : ReadResult()
|
||||
data class Failure(val exception: Exception) : ReadResult()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue