Android NFC 不读取通过主机卡模拟 (HCE) 模拟的 NDEF 标签,iPhone 可以

问题描述

我正在尝试通过 Android 上的 HCE 模拟 NDEF 标签。我可以使用 iPhone 读取模拟标签,但是当我尝试使用 Android 读取时没有任何反应。

我没有使用我的应用阅读,只是使用认的操作系统行为或任何下载的应用阅读。

用于测试它的所有 Android 都是 android 10 或更高版本,因此 android beam 应该不成问题,我正在从更新的像素 3a XL 进行模拟。

我的代码基于此项目 from github,克隆该项目会产生相同的结果,我可以使用 iPhone 读取它,但使用 Android 手机读取它似乎没有任何反应。

从 Android 读取 HCE 是否有任何限制?

这是我的服务

import android.content.Context
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.cardemulation.HostApduService
import android.os.Bundle
import android.util.Log
import java.io.UnsupportedEncodingException
import java.math.BigInteger
import java.util.*


/**
 * Created by Qifan on 05/12/2018.
 */

class NfcEmulatorService : HostApduService() {

    private val TAG = "HostApduService"

    private val APDU_SELECT = byteArrayOf(
            0x00.toByte(),// CLA   - Class - Class of instruction
            0xA4.toByte(),// INS   - Instruction - Instruction code
            0x04.toByte(),// P1    - Parameter 1 - Instruction parameter 1
            0x00.toByte(),// P2    - Parameter 2 - Instruction parameter 2
            0x07.toByte(),// Lc field  - Number of bytes present in the data field of the command
            0xD2.toByte(),0x76.toByte(),0x00.toByte(),0x85.toByte(),0x01.toByte(),// NDEF Tag Application name
            0x00.toByte()  // Le field  - Maximum number of bytes expected in the data field of the response to the command
    )

    private val CAPABILITY_CONTAINER_OK = byteArrayOf(
            0x00.toByte(),// CLA   - Class - Class of instruction
            0xa4.toByte(),// INS   - Instruction - Instruction code
            0x00.toByte(),// P1    - Parameter 1 - Instruction parameter 1
            0x0c.toByte(),// P2    - Parameter 2 - Instruction parameter 2
            0x02.toByte(),// Lc field  - Number of bytes present in the data field of the command
            0xe1.toByte(),0x03.toByte() // file identifier of the CC file
    )

    private val READ_CAPABILITY_CONTAINER = byteArrayOf(
            0x00.toByte(),// CLA   - Class - Class of instruction
            0xb0.toByte(),// P2    - Parameter 2 - Instruction parameter 2
            0x0f.toByte()  // Lc field  - Number of bytes present in the data field of the command
    )

    // In the scenario that we have done a CC read,the same byte[] match
    // for ReadBinary would trigger and we don't want that in succession
    private var READ_CAPABILITY_CONTAINER_CHECK = false

    private val READ_CAPABILITY_CONTAINER_RESPONSE = byteArrayOf(
            0x00.toByte(),0x11.toByte(),// cclEN length of the CC file
            0x20.toByte(),// Mapping Version 2.0
            0xFF.toByte(),0xFF.toByte(),// MLe maximum
            0xFF.toByte(),// MLc maximum
            0x04.toByte(),// T field of the NDEF File Control TLV
            0x06.toByte(),// L field of the NDEF File Control TLV
            0xE1.toByte(),0x04.toByte(),// File Identifier of NDEF file
            0xFF.toByte(),0xFE.toByte(),// Maximum NDEF file size of 65534 bytes
            0x00.toByte(),// Read access without any security
            0xFF.toByte(),// Write access without any security
            0x90.toByte(),0x00.toByte() // A_OKAY
    )

    private val NDEF_SELECT_OK = byteArrayOf(
            0x00.toByte(),// Instruction byte (INS) for Select command
            0x00.toByte(),// Parameter byte (P1),select by identifier
            0x0c.toByte(),select by identifier
            0x02.toByte(),// Lc field  - Number of bytes present in the data field of the command
            0xE1.toByte(),0x04.toByte() // file identifier of the NDEF file retrieved from the CC file
    )

    private val NDEF_READ_BINARY = byteArrayOf(
            0x00.toByte(),// Class byte (CLA)
            0xb0.toByte() // Instruction byte (INS) for ReadBinary command
    )

    private val NDEF_READ_BINARY_NLEN = byteArrayOf(
            0x00.toByte(),// Class byte (CLA)
            0xb0.toByte(),// Instruction byte (INS) for ReadBinary command
            0x00.toByte(),// Parameter byte (P1,P2),offset inside the CC file
            0x02.toByte()  // Le field
    )

    private val A_OKAY = byteArrayOf(
            0x90.toByte(),// SW1   Status byte 1 - Command processing status
            0x00.toByte()   // SW2  Status byte 2 - Command processing qualifier
    )

    private val A_ERROR = byteArrayOf(
            0x6A.toByte(),// SW1   Status byte 1 - Command processing status
            0x82.toByte()   // SW2  Status byte 2 - Command processing qualifier
    )

    private val NDEF_ID = byteArrayOf(0xE1.toByte(),0x04.toByte())

    private var url = "https://facebook.com"

    private var NDEF_URI = NdefMessage(NdefRecord.createUri(url))


    private var NDEF_URI_BYTES = NDEF_URI.toByteArray()
    private var NDEF_URI_LEN = fillByteArrayToFixedDimension(
            BigInteger.valueOf(NDEF_URI_BYTES.size.toLong()).toByteArray(),2
    )




    override fun onCreate() {
        super.onCreate()

        val prefs = getSharedPreferences("FlutterSharedPreferences",Context.MODE_PRIVATE)
        url = prefs.getString("Flutter.url","").toString()
        Log.i(TAG,"Service created with urlExtracted: " + url)

        if (url != "https://facebook.com") {
            NDEF_URI = NdefMessage(NdefRecord.createUri(url))
            NDEF_URI_BYTES = NDEF_URI.toByteArray()
            NDEF_URI_LEN = fillByteArrayToFixedDimension(
                    BigInteger.valueOf(NDEF_URI_BYTES.size.toLong()).toByteArray(),2
            )
        }
    }


    override fun processCommandApdu(commandApdu: ByteArray,extras: Bundle?): ByteArray {

        //
        // The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow"
        // in the NFC Forum specification
        //
        Log.i(TAG,"processCommandApdu() | incoming commandApdu: " + commandApdu.toHex())

        //
        // First command: NDEF Tag Application select (Section 5.5.2 in NFC Forum spec)
        //
        if (Arrays.equals(APDU_SELECT,commandApdu)) {
            Log.i(TAG,"APDU_SELECT triggered. Our Response: " + A_OKAY.toHex())
            return A_OKAY
        }

        //
        // Second command: Capability Container select (Section 5.5.3 in NFC Forum spec)
        //
        if (Arrays.equals(CAPABILITY_CONTAINER_OK,"CAPABILITY_CONTAINER_OK triggered. Our Response: " + A_OKAY.toHex())
            return A_OKAY
        }

        //
        // Third command: ReadBinary data from CC file (Section 5.5.4 in NFC Forum spec)
        //
        if (Arrays.equals(
                        READ_CAPABILITY_CONTAINER,commandApdu
                ) && !READ_CAPABILITY_CONTAINER_CHECK
        ) {
            Log.i(
                    TAG,"READ_CAPABILITY_CONTAINER triggered. Our Response: " + READ_CAPABILITY_CONTAINER_RESPONSE.toHex()
            )
            READ_CAPABILITY_CONTAINER_CHECK = true
            return READ_CAPABILITY_CONTAINER_RESPONSE
        }

        //
        // Fourth command: NDEF Select command (Section 5.5.5 in NFC Forum spec)
        //
        if (Arrays.equals(NDEF_SELECT_OK,"NDEF_SELECT_OK triggered. Our Response: " + A_OKAY.toHex())
            return A_OKAY
        }

        if (Arrays.equals(NDEF_READ_BINARY_NLEN,commandApdu)) {
            // Build our response
            val response = ByteArray(NDEF_URI_LEN.size + A_OKAY.size)
            System.arraycopy(NDEF_URI_LEN,response,NDEF_URI_LEN.size)
            System.arraycopy(A_OKAY,NDEF_URI_LEN.size,A_OKAY.size)

            Log.i(TAG,"NDEF_READ_BINARY_NLEN triggered. Our Response: " + response.toHex())

            READ_CAPABILITY_CONTAINER_CHECK = false
            return response
        }

        if (Arrays.equals(commandApdu.sliceArray(0..1),NDEF_READ_BINARY)) {
            val offset = commandApdu.sliceArray(2..3).toHex().toInt(16)
            val length = commandApdu.sliceArray(4..4).toHex().toInt(16)

            val fullResponse = ByteArray(NDEF_URI_LEN.size + NDEF_URI_BYTES.size)
            System.arraycopy(NDEF_URI_LEN,fullResponse,NDEF_URI_LEN.size)
            System.arraycopy(
                    NDEF_URI_BYTES,NDEF_URI_BYTES.size
            )

            Log.i(TAG,"NDEF_READ_BINARY triggered. Full data: " + fullResponse.toHex())
            Log.i(TAG,"READ_BINARY - OFFSET: " + offset + " - LEN: " + length)

            val slicedResponse = fullResponse.sliceArray(offset until fullResponse.size)

            // Build our response
            val realLength = if (slicedResponse.size <= length) slicedResponse.size else length
            val response = ByteArray(realLength + A_OKAY.size)

            System.arraycopy(slicedResponse,realLength)
            System.arraycopy(A_OKAY,realLength,"NDEF_READ_BINARY triggered. Our Response: " + response.toHex())

            READ_CAPABILITY_CONTAINER_CHECK = false
            return response
        }

        //
        // We're doing something outside our scope
        //
        Log.wtf(TAG,"processCommandApdu() | I don't kNow what's going on!!!")
        return A_ERROR
    }

    override fun onDeactivated(reason: Int) {
        Log.i(TAG,"onDeactivated() Fired! Reason: $reason")
    }


    private val HEX_CHARS = "0123456789ABCDEF".tochararray()

    fun ByteArray.toHex(): String {
        val result = StringBuffer()

        forEach {
            val octet = it.toInt()
            val firstIndex = (octet and 0xF0).ushr(4)
            val secondindex = octet and 0x0F
            result.append(HEX_CHARS[firstIndex])
            result.append(HEX_CHARS[secondindex])
        }

        return result.toString()
    }

    fun String.hexStringToByteArray(): ByteArray {

        val result = ByteArray(length / 2)

        for (i in 0 until length step 2) {
            val firstIndex = HEX_CHARS.indexOf(this[i]);
            val secondindex = HEX_CHARS.indexOf(this[i + 1]);

            val octet = firstIndex.shl(4).or(secondindex)
            result.set(i.shr(1),octet.toByte())
        }

        return result
    }

    fun createTextRecord(language: String,text: String,id: ByteArray): NdefRecord {
        val languageBytes: ByteArray
        val textBytes: ByteArray
        try {
            languageBytes = language.toByteArray(charset("US-ASCII"))
            textBytes = text.toByteArray(charset("UTF-8"))
        } catch (e: UnsupportedEncodingException) {
            throw AssertionError(e)
        }

        val recordPayload = ByteArray(1 + (languageBytes.size and 0x03F) + textBytes.size)

        recordPayload[0] = (languageBytes.size and 0x03F).toByte()
        System.arraycopy(languageBytes,recordPayload,1,languageBytes.size and 0x03F)
        System.arraycopy(
                textBytes,1 + (languageBytes.size and 0x03F),textBytes.size
        )

        return NdefRecord(NdefRecord.TNF_WELL_KNowN,NdefRecord.RTD_TEXT,id,recordPayload)
    }

    fun fillByteArrayToFixedDimension(array: ByteArray,fixedSize: Int): ByteArray {
        if (array.size == fixedSize) {
            return array
        }

        val start = byteArrayOf(0x00.toByte())
        val filledArray = ByteArray(start.size + array.size)
        System.arraycopy(start,filledArray,start.size)
        System.arraycopy(array,start.size,array.size)
        return fillByteArrayToFixedDimension(filledArray,fixedSize)
    }
}

在此先感谢,我是应用程序的新手并且愿意学习,任何帮助将不胜感激

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...