Skip to content

Commit 7d1e8aa

Browse files
authored
(android) add support for NFC Forum Type 1/2 tags (#809)
This commits adds a fallback path that uses the Android Ndef API to read NDEF messages directly from Type 1/2 tags. The NFC reader previously only accepted ISO-DEP (NFC Forum Type 4) tags such as NTAG424-DNA and Bolt Cards. Tags based on the NFC Forum Type 2 spec, like the widely-used NTAG213, NTAG215 and NTAG216 family, were silently dropped.
1 parent d0e3c9d commit 7d1e8aa

1 file changed

Lines changed: 56 additions & 11 deletions

File tree

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/utils/nfc/NfcReaderCallback.kt

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.nfc.NdefMessage
2020
import android.nfc.NfcAdapter
2121
import android.nfc.Tag
2222
import android.nfc.tech.IsoDep
23+
import android.nfc.tech.Ndef
2324
import fr.acinq.lightning.utils.toByteVector
2425
import fr.acinq.phoenix.android.components.nfc.NfcState
2526
import fr.acinq.phoenix.android.components.nfc.NfcStateRepository
@@ -29,30 +30,47 @@ import org.slf4j.LoggerFactory
2930
/**
3031
* This class retrieves the first NDEF message in an NFC tag.
3132
*
32-
* Sends a sequence of APDU commands to the tag, mirroring what's done in [HceService].
33+
* Two tag types are supported:
34+
* - ISO-DEP tags (NFC Forum Type 4, e.g. NTAG424-DNA, Bolt Cards): communicates via a sequence
35+
* of APDU commands, mirroring what's done in [HceService].
36+
* - Plain NDEF tags (NFC Forum Type 1/2, e.g. NTAG213/215/216): reads the NDEF message directly
37+
* using the Android [Ndef] API without APDU negotiation.
3338
*/
3439
class NfcReaderCallback(val onFoundData: (String) -> Unit) : NfcAdapter.ReaderCallback {
3540
private val log = LoggerFactory.getLogger(this::class.java)
3641

3742
@OptIn(ExperimentalStdlibApi::class)
3843
override fun onTagDiscovered(tag: Tag?) {
3944
log.info("discovered tag=$tag")
45+
46+
if (!NfcStateRepository.isReading()) {
47+
log.info("aborting tag discovery: nfc_state=${NfcStateRepository.state.value}")
48+
return
49+
}
50+
51+
// Prefer ISO-DEP (NFC Forum Type 4: NTAG424-DNA, Bolt Cards, …)
4052
val isoDep = IsoDep.get(tag)
41-
if (isoDep == null) {
42-
log.info("aborting tag discovery: tag does not support iso-dep")
53+
if (isoDep != null) {
54+
readIsoDepTag(isoDep)
4355
return
4456
}
4557

46-
if (!NfcStateRepository.isReading()) {
47-
log.info("aborting tag discovery: nfc_state=${NfcStateRepository.state.value}")
48-
isoDep.close()
58+
// Fallback: plain NDEF (NFC Forum Type 1/2: NTAG213, NTAG215, NTAG216, …)
59+
val ndef = Ndef.get(tag)
60+
if (ndef != null) {
61+
readNdefTag(ndef)
4962
return
5063
}
5164

65+
log.info("aborting tag discovery: tag does not support iso-dep or plain ndef")
66+
}
67+
68+
@OptIn(ExperimentalStdlibApi::class)
69+
private fun readIsoDepTag(isoDep: IsoDep) {
5270
try {
53-
log.debug("connecting to tag={}", tag)
71+
log.debug("connecting to iso-dep tag")
5472
isoDep.connect()
55-
log.info("nfc reader connected, starting communication...")
73+
log.info("iso-dep reader connected, starting communication...")
5674

5775
val selectCommand = ApduCommands.SELECT_AID
5876
log.debug("SEND select aid: ${selectCommand.toByteArray().toHexString()}")
@@ -120,7 +138,7 @@ class NfcReaderCallback(val onFoundData: (String) -> Unit) : NfcAdapter.ReaderCa
120138
}
121139

122140
val m = NdefMessage(result)
123-
log.info("successfully read tag, found ndef_message=$m")
141+
log.info("successfully read iso-dep tag, found ndef_message=$m")
124142
val d = m.records.mapNotNull {
125143
NdefParser.parseNdefRecord(it)
126144
}
@@ -129,13 +147,40 @@ class NfcReaderCallback(val onFoundData: (String) -> Unit) : NfcAdapter.ReaderCa
129147
}
130148

131149
} catch (e: Exception) {
132-
log.error("failed to read tag: ", e)
150+
log.error("failed to read iso-dep tag: ", e)
133151
} finally {
134152
isoDep.close()
135153
if (NfcStateRepository.isReading()) {
136154
NfcStateRepository.updateState(NfcState.Inactive)
137155
}
138-
log.info("terminated nfc reader callback")
156+
log.info("terminated iso-dep reader callback")
157+
}
158+
}
159+
160+
private fun readNdefTag(ndef: Ndef) {
161+
try {
162+
log.debug("connecting to ndef tag type=${ndef.type}")
163+
ndef.connect()
164+
log.info("ndef reader connected, reading message...")
165+
166+
val message = ndef.ndefMessage
167+
if (message == null) {
168+
log.info("ndef tag contains no message")
169+
return
170+
}
171+
172+
log.info("successfully read ndef tag, found message=$message")
173+
val data = message.records.mapNotNull { NdefParser.parseNdefRecord(it) }
174+
data.firstOrNull()?.let { onFoundData(it) }
175+
176+
} catch (e: Exception) {
177+
log.error("failed to read ndef tag: ", e)
178+
} finally {
179+
ndef.close()
180+
if (NfcStateRepository.isReading()) {
181+
NfcStateRepository.updateState(NfcState.Inactive)
182+
}
183+
log.info("terminated ndef reader callback")
139184
}
140185
}
141186
}

0 commit comments

Comments
 (0)