DamierMay 26, 2026

NFC behavior changes in Android 17


In May 2026, I realized that all of a sudden, NFC check-ins for Damier no longer seemed to function correctly. At first, I assumed it was just a writing issue, since tapping a chip with Damier being in writing mode would not update the ID, but that was wrong. The chip was being written correctly. I simply didn't verify it and assumed the writing was failing because reads were broken.

The setup

Damier used Cue, a small NFC library I wrote specifically for it. Tags are written in the format cue://damier/<uuid> using NdefRecord.createUri(), producing a TNF_WELL_KNOWN + RTD_URI record. Reading goes through Android's standard NDEF intent dispatch system: the OS scans the tag, matches it against the app's intent filter, and fires ACTION_NDEF_DISCOVERED into onNewIntent().

Or at least, that's how it's supposed to work.

Debugging

The first thing I checked was whether onNewIntent() was even firing. It wasn't. Android wasn't dispatching the NFC intent to Damier at all, which immediately ruled out any bug in Cue's parsing logic, since that code only runs after the intent arrives.

I checked everything I could think of:

  • the NDEF_DISCOVERED intent filter was intact, with the correct scheme and host within the Merged Manifest.
  • I checked every app with NFC permission using Exodus to check if other apps are intercepting the tag
  • Damier uses enableReaderMode for writing chips. If reader mode stays active, it intercepts tags before the system dispatch can fire. I confirmed the write state was correctly returning to Idle and disableReaderMode was being called.
  • NFC Tools confirmed the tag contained exactly cue://damier/<uuid>
  • isTagIntentAllowed() returned true. Android 16 introduced a new NFC tag scanning allowlist under Settings → Apps → Special app access → Launch via NFC. Damier was allowed.
  • I tested whether Android 16 was routing cue:// as ACTION_VIEW (documented for http:// and https:// schemes). It wasn't, ACTION_VIEW didn't fire either.
  • I temporarily rewrote a tag with MIME type application/de.lijucay.damier using NFC Tools and dded the corresponding filter. Still no dispatch.
  • I stripped the filter down to just NDEF_DISCOVERED with no <data> at all. Still nothing.
  • I built a completely empty app with a single Activity, a single NFC filter, and one log line in onNewIntent(). Tapping the tag produced action=android.intent.action.MAIN, meaning Android was launching the app but discarding the NFC intent entirely.

At this point I was fairly convinced it was a Pixel 10 Pro device bug. So at 3 am, I reflashed the operating system.

After the flash

Immediately after the flash, before any updates, NFC worked perfectly. I tapped a chip, Damier opened, check-in logged. The next day, I finished setting up the phone and installed the pending Google Play System Update. A few hours later, I tapped a chip, and it didn't work again. The only thing that changed this time was the Google Play System update.

This time I tried something I had dismissed too early: I set targetSdk back to 36. NFC immediately started working again. Bumping it back to 37 broke it again.

The actual cause

The Google Play System Update had backported a behavior change from Android 17 (API level 37): apps targeting SDK 37 now require the android.permission.DISPATCH_NFC_MESSAGE permission on any Activity that receives NFC intents.

This is documented in NFC Basics, but it was not listed on Behavior changes: Apps targeting Android 17 or higher, which is the page developers actually check when bumping their target SDK. It was quietly added to a separate documentation page. Two days of debugging for an undocumented breaking change.

From the docs:

Starting Android 17 (API level 37), for an activity to be dispatched an NFC intent, if the app targets SDK > Build.VERSION_CODES.BAKLAVA, it must be protected by the android.permission.DISPATCH_NFC_MESSAGE permission. This ensures that only the NFC system service can dispatch intents to your activity. Additionally, the system will not dispatch NFC intents to applications that are in a stopped state (e.g. if the application has never been launched by the user or has been force-stopped).

The reason you can't just add this permission to MainActivity directly is that android:permission on an Activity restricts who can start it. With DISPATCH_NFC_MESSAGE set, only the NFC system service can launch that Activity, meaning tapping the app icon would produce "App not installed". The launcher doesn't hold that permission, so it can't start the Activity at all.

The fix

The solution is a dedicated NfcDispatchActivity that holds the permission, receives the NFC intent, forwards it to MainActivity, and immediately finishes itself.

In AndroidManifest.xml:

<activity
    android:name=".nfc.NfcDispatchActivity"
    android:exported="true"
    android:permission="android.permission.DISPATCH_NFC_MESSAGE">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="cue" android:host="@string/host"/>
    </intent-filter>
</activity>

And the Activity itself:

class NfcDispatchActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val nfcIntent = intent

        startActivity(
            Intent(this, MainActivity::class.java).apply {
                action = nfcIntent.action
                data = nfcIntent.data
                putExtras(nfcIntent)
                addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            }
        )

        finish()
    }
}

MainActivity receives the forwarded intent in onNewIntent() as normal, and nothing else in the app needs to change.

It's not elegant, but it works, and it keeps all existing functionality intact. If Google had put this in their behavior changes list, it would have been a 10-minute fix instead of a two-day investigation.

Summary

If your app targets SDK 37 and NFC dispatch suddenly stopped working after a Google Play System Update — even though your manifest, filters, and tag contents are all correct — this is almost certainly the cause. Add a dedicated NfcDispatchActivity with android:permission="android.permission.DISPATCH_NFC_MESSAGE" and forward the intent to your main Activity.

And maybe file some feedback with Google about documenting breaking changes where developers will actually find them.