Is NFC NDEF supported on Kitkat?

298 Views Asked by At

I am trying to read NFC NDEF Messages using a MobiPrint Device running on KitKat.

I followed the instructions in the Docs, but I can't seem to get it to work on Kitkat.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pos">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.NFC" />

    <uses-feature android:name="android.hardware.nfc" android:required="true"/>

    <application
        android:name=".App"
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Pos">
        <activity
            android:name=".ui.views.main.MainActivity"
            android:theme="@style/Theme.Pos.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
   
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>
    </application>
</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private var adapter: NfcAdapter? = null
    private var pendingIntent: PendingIntent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        adapter = NfcAdapter.getDefaultAdapter(this)

        pendingIntent = PendingIntent.getActivity(this, 0,
            Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
    }

    override fun onResume() {
        super.onResume()
        adapter?.enableForegroundDispatch(this, pendingIntent, null, null)
    }

    override fun onPause() {
        super.onPause()
        adapter?.disableForegroundDispatch(this)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)

        val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
        Log.d("NFC_NDEF_MESSAGES", rawMessages.toString())
        Log.d("NFC_NDEF_ACTION", intent.action.toString())
    }
}

Logcat Messages

MobiPrint Device with Kitkat:

NFC_NDEF_MESSAGES: null NFC_NDEF_ACTION: android.nfc.action.TAG_DISCOVERED

S7 Active with Marshmallow:

NFC_NDEF_MESSAGES: [Landroid.os.Parcelable;@6405e69 NFC_NDEF_ACTION: android.nfc.action.NDEF_DISCOVERED

Is NDEF not supported on Kitkat?

2

There are 2 best solutions below

5
Andrew On BEST ANSWER

The NXP MIFARE Classic series of NFC Cards do not conform to the NFC Forum standards (the specification pre-dates the NFC Standards)

Thus some hardware does not support the NXP MIFARE Classic series of NFC Cards and if you look at the Android docs https://developer.android.com/reference/android/nfc/tech/MifareClassic

Implementation of this class on a Android NFC device is optional. If it is not implemented, then MifareClassic will never be enumerated in Tag#getTechList. If it is enumerated, then all MifareClassic I/O operations will be supported, and Ndef#MIFARE_CLASSIC NDEF tags will also be supported. In either case, NfcA will also be enumerated on the tag, because all MIFARE Classic tags are also NfcA.

So support for this type of card is Optional in Android and as it says if it is supported then the higher level NDEF protocol will be support on top of it (but as in your case it is not supported at the MifareClassic level then NDEF won't be supported on top)

This is probably why you are getting the android.nfc.action.TAG_DISCOVERED message as this comes from the low level NfcA which is a low very protocol which MifareClassic has in common with other NFC Specification cards.

Solution to get the MobiPrint Device to read NFC tag is use buy different NFC Tags unfortunately, this is nothing to do with the Android Version (Even some later Android hardware won't read them), I Suggest using an NFC Forum specification type Tag. A close match is the NXP TAG 216 card (NFC Forum Type 2) or the NXP Desfire (NFC Forum Type 4)

1
Martin Zeitler On

For some reason it detects it as TAG_DISCOVERED (which usually hints for an absent NDEF format). And even when if fails to detect it as MifareUltralight, one still can use it as NfcA, with the CC at page 3, data-area starts on page 4 (no matter if formatting or reading the format). When the tags are being readable on later versions of Android, these should definitely count as being "readable".

All it needs is a KitKat implementation, which deals with MifareUltralight or NfcA.

One can fall-back without checking for the API level:

when (intent.action.toString()) {
    android.nfc.action.NDEF_DISCOVERED -> {
        /* handle NDEF messages (business as usual). */
    }
    android.nfc.action.TAG_DISCOVERED -> {

        /* Check if NDEF format is present and read it. */
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        MifareUltralight mifare = MifareUltralight.get(tag)
        mifare.connect()
        ...
        mifare.close()

        /* When MifareUltralight is null, try NfcA: */
        // NfcA nfcA = NfcA.get(tag)
    }
    else -> {
        print("unknown intent action")
    }
}

MifareUltralight knows pages, NfcA is rather low-level and only knows ByteArray payloads.
Instead of suggesting different tags (which may trigger just the same Intent, which only would have to be handled accordingly), one could instead raise the minApiLevel and stop supporting KitKat altogether (the Intent still may be triggered for empty tags). I'd have NTAG216 and at least a physical API 22 device - to see which Intent it would actually trigger, but no time. One would need a physical API 19 device to properly reproduce this.