0

I am currently developing an Android NFC application. This application contains a NavigationDrawer in which I can access 3 different fragments which each correspond to 3 different NFC features.

First, I want to verify that my application can scan an NFC tag (here I have an NXP NTAG 5 boost tag, which is an NFC Forum Type 5 tag).

My problem is that when my application is running, the onNewIntent of my MainActivity is never called.

I'm sure it's not due to the hardware as I can detect this tag with common applications, but it may be due to parameters I passed to a function in the NfcManager file, like activity.applicationContext but I'm not sure.

For now, I specify that I don't want a particular activity to launch when a tag is detected, I just want to detect an NFC tag when the application runs.

Can you help me?

MainActivity:

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration

    private val TAG = MainActivity::class.java.simpleName
    var tag: Tag? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        checkNFC(this)
        setupNFC(this)

        configureToolbar()

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(setOf(
                R.id.nav_memory, R.id.nav_tag, R.id.nav_product), drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        return true
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

    private fun configureToolbar() {
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == ENABLE_NFC_REQUEST_CODE) {
            onActivityResultOutSourced(this)
        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
    }

    override fun onResume() {
        super.onResume()
        onResumeOutSourced(this)
    }

    override fun onPause() {
        super.onPause()
        onPauseOutSourced(this)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        Log.d(TAG, "Card ID: " + Tools.byteArrayToHex(tag!!.id))
        val techList = tag!!.techList

        //Check that the discovered tag is a vicinity tag
        if (techList[0] == "android.nfc.tech.NfcV") {
            val tagUid = tag!!.id
            nfcvTag = NfcV.get(tag)

            //ISO/IEC 15693 tags can be operated in two modes:
            // Select mode and Addressed mode.
            //To work in the select mode it is needed to send a SELECT
            // command at the beginning of communic.
            //In the address mode, the tag UID is sent within each command.
            //This application works in SELECT MODE.
            val select_command: ByteArray = RFCommands.cmd_select
            System.arraycopy(tagUid, 0, select_command, 2, 8)
            if (nfcvTag != null) {
                try {
                    nfcvTag!!.connect()
                    val select_respo = nfcvTag!!.transceive(select_command)
                    Log.d(TAG, "Select response: " +
                            Tools.byteArrayToHex(select_respo))
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }

    companion object {
        const val ENABLE_NFC_REQUEST_CODE = 0x11
        private var nfcvTag: NfcV? = null
    }
}

NfcManager:

    class NfcManager {
        companion object {
            private var mNfcAdapter: NfcAdapter? = null
            private var mPendingIntent: PendingIntent? = null
            private lateinit var writeTagFilters: Array<IntentFilter>
            private lateinit var mTechLists: Array<Array<String>>

        /**
         * Check the availability of NFC interface and let the user enable them
         * if not active during the activity creation
         */
        fun checkNFC(@NonNull activity: Activity) {
            if (activity.packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
                mNfcAdapter = NfcAdapter.getDefaultAdapter(activity.applicationContext)
                if (mNfcAdapter != null && !mNfcAdapter!!.isEnabled) {
                    AlertDialog.Builder(activity.applicationContext)
                            .setTitle(activity.resources.getString(R.string.dialog_nfc_not_enabled_title))
                            .setMessage(activity.resources.getString(R.string.dialog_nfc_not_enabled_msg))
                            .setPositiveButton(activity.resources.getString(R.string.dialog_nfc_not_enabled_positive_btn)
                            ) { _, _ -> activity.startActivityForResult(Intent(Settings.ACTION_NFC_SETTINGS), MainActivity.ENABLE_NFC_REQUEST_CODE) }
                            .setNegativeButton(activity.resources.getString(R.string.dialog_nfc_not_enabled_negative_btn)
                            ) { _, _ ->
                                Toast.makeText(activity.applicationContext, activity.resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
                                activity.finish()
                            }.show()
                }
            } else {
                Toast.makeText(activity.applicationContext, activity.resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
                activity.finish()
            }
        }

        /**
         * Create a generic PendingIntent that will be delivered to this activity.
         * The NFC stack will fill in the intent with the details of the discovered
         * tag before delivering it to this activity.
         */
        fun setupNFC(@NonNull activity: Activity) {
            mPendingIntent = PendingIntent.getActivity(activity, 0, Intent(
                    activity, javaClass)
                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
            val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
            writeTagFilters = arrayOf(tagDetected)
            mTechLists = arrayOf(arrayOf(
                    NfcV::class.java.name
            ))
        }

        fun onActivityResultOutSourced(@NonNull activity: Activity) {
            mNfcAdapter = NfcAdapter.getDefaultAdapter(activity)
            if (mNfcAdapter!!.isEnabled) {
                Toast.makeText(activity.applicationContext, activity.resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
                activity.finish()
            }
        }

        fun onResumeOutSourced(@NonNull activity: Activity) {
            if (mNfcAdapter != null) {
                mNfcAdapter!!.enableForegroundDispatch(activity, mPendingIntent, writeTagFilters, mTechLists)
            }
        }

        fun onPauseOutSourced(@NonNull activity: Activity) {
            if (mNfcAdapter != null) mNfcAdapter!!.disableForegroundDispatch(activity)
        }
    }
}

EDIT:

The objective of the "NfcManager" class was to encapsulate as much as possible the NFC functions, in order to avoid having them in the "MainActivity".

The problem is that the previous code doesn't work, while the following code, in which the logic is contained in the "MainActivity" works well. I really don't understand what the problem is.

MainActivity:

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration

    private val TAG: String = this::class.java.simpleName
    private val ENABLE_NFC_REQUEST_CODE = 0x11
    private var mNfcAdapter: NfcAdapter? = null
    private var nfcvTag: NfcV? = null
    var tag: Tag? = null
    private var mPendingIntent: PendingIntent? = null
    private lateinit var writeTagFilters: Array<IntentFilter>
    private lateinit var mTechLists: Array<Array<String>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        checkNFC()
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this)
        setNfcIntent()

        configureToolbar()

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(setOf(
                R.id.nav_memory, R.id.nav_tag, R.id.nav_product), drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        return true
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

    private fun configureToolbar() {
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
    }

    private fun setNfcIntent() {
        // Create a generic PendingIntent that will be delivered to this activity. The NFC stack will fill
        // in the intent with the details of the discovered tag before delivering it to this activity.
        mPendingIntent = PendingIntent.getActivity(this, 0, Intent(
                applicationContext, javaClass)
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
        val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
        writeTagFilters = arrayOf(tagDetected)
        mTechLists = arrayOf(arrayOf(
                NfcV::class.java.name
        ))
    }

    override fun onActivityResult(requestCode: Int,
                                  resultCode: Int, data: Intent?) {
        if (requestCode == ENABLE_NFC_REQUEST_CODE) {
            mNfcAdapter = NfcAdapter.getDefaultAdapter(this)
            if (mNfcAdapter!!.isEnabled) {
                Toast.makeText(applicationContext, resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
                finish()
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
    }

    /**
     * Check the availability of NFC and BLE interfaces and let the user enable them
     * if not active during the activity creation
     */
    private fun checkNFC() {
        if (packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
            mNfcAdapter = NfcAdapter.getDefaultAdapter(this)
            if (mNfcAdapter != null && !mNfcAdapter!!.isEnabled) {
                AlertDialog.Builder(this)
                        .setTitle(resources.getString(R.string.dialog_nfc_not_enabled_title))
                        .setMessage(resources.getString(R.string.dialog_nfc_not_enabled_msg))
                        .setPositiveButton(resources.getString(R.string.dialog_nfc_not_enabled_positive_btn)
                        ) { dialog, which -> startActivityForResult(Intent(Settings.ACTION_NFC_SETTINGS), ENABLE_NFC_REQUEST_CODE) }
                        .setNegativeButton(resources.getString(R.string.dialog_nfc_not_enabled_negative_btn)
                        ) { dialog, which ->
                            Toast.makeText(applicationContext, resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
                            finish()
                        }.show()
            }
        } else {
            Toast.makeText(applicationContext, resources.getString(R.string.nfc_not_enabled), Toast.LENGTH_LONG).show()
            finish()
        }
    }

    override fun onResume() {
        super.onResume()
        if (mNfcAdapter != null) {
            mNfcAdapter!!.enableForegroundDispatch(this, mPendingIntent, writeTagFilters, mTechLists)
        }
    }

    override fun onPause() {
        super.onPause()
        if (mNfcAdapter != null) mNfcAdapter!!.disableForegroundDispatch(this)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        Log.d(TAG, "Card ID: " + Tools.byteArrayToHex(tag!!.id))
        val techList = tag!!.techList

        //Check that the discovered tag is a vicinity tag
        if (techList[0] == "android.nfc.tech.NfcV") {
            val tagUid = tag!!.id
            nfcvTag = NfcV.get(tag)

            //ISO/IEC 15693 tags can be operated in two modes:
            // Select mode and Addressed mode.
            //To work in the select mode it is needed to send a SELECT
            // command at the beginning of communic.
            //In the address mode, the tag UID is sent within each command.
            //This application works in SELECT MODE.
            val select_command: ByteArray = RFCommands.cmd_select
            System.arraycopy(tagUid, 0, select_command, 2, 8)
            if (nfcvTag != null) {
                try {
                    nfcvTag!!.connect()
                    val select_respo: ByteArray = nfcvTag!!.transceive(select_command)
                    Log.d(TAG, "Select response: " +
                            Tools.byteArrayToHex(select_respo))
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }
}
Martin Denion
  • 352
  • 1
  • 3
  • 22
  • As first thing you could start the dispatcher with this: `mNfcAdapter!!.enableForegroundDispatch(activity, mPendingIntent, null, null)` and be sure that this line is called with a breakpoint. Moreover in some devices there is a bug where if your restart the app from Android Studio while you were debugging and NFC dispatcher was active, it will not send you any NFC event unless you disable and re-enable the NFC from settings – MatPag Apr 29 '21 at 13:44
  • If you are targeting Android API 19 or greater I would not use `enableForegroundDispatch` at all as this is very unreliable especially if you are doing anything more complicated than reading Ndef data. As you are doing additional transceive commands you will find it very unreliable in real users hand as they will be prompted to remove the card from the RF field before the transceive has happened. It is much better to use `enableReaderMode` API. This might not be the cause of your problems but in the long term `enableReaderMode` API is better. – Andrew Apr 29 '21 at 14:13
  • Thanks guys, I read your comments, but it seems to be a scope problem – Martin Denion Apr 30 '21 at 09:42

0 Answers0