Skip to content

Commit 7ba2fd5

Browse files
authored
Merge pull request #193 from nfcim/feature/android-streaming
Feature/android streaming
2 parents ddea80c + e02e2e2 commit 7ba2fd5

File tree

13 files changed

+332
-169
lines changed

13 files changed

+332
-169
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,11 @@ This is a release candidate for 3.6.0. Please test it and report any issues.
191191
* Related issues / PRs: #179 #184, #186, #187
192192
* Now requiring Java 17, Gradle 8.9, MinSDKVer 26, AGP 8.7, Kotlin 2.1.0
193193
* Add Swift package manager support for iOS plugin, bump dependencies
194+
195+
## 3.6.0-rc.3
196+
197+
This is a release candidate for 3.6.0. Please test it and report any issues.
198+
199+
* All 3.6.0-rc.2 changes are included
200+
* Fix WebUSB interop on Web
201+
* Add support for foreground polling on Android (#16, #179)

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,7 @@ Refer to the [documentation](https://pub.dev/documentation/flutter_nfc_kit/) for
6363
### Error codes
6464

6565
We use error codes with similar meaning as HTTP status code. Brief explanation and error cause in string (if available) will also be returned when an error occurs.
66+
67+
### Operation Mode
68+
69+
We provide two operation modes: polling (default) and event streaming. Both can give the same `NFCTag` object. Please see [example](example/example.md) for more details.

android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt

Lines changed: 178 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.nfc.NdefMessage
66
import android.nfc.NdefRecord
77
import android.nfc.NfcAdapter
88
import android.nfc.NfcAdapter.*
9+
import android.nfc.Tag
910
import android.nfc.tech.*
1011
import android.os.Handler
1112
import android.os.HandlerThread
@@ -24,6 +25,9 @@ import io.flutter.plugin.common.MethodCall
2425
import io.flutter.plugin.common.MethodChannel
2526
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
2627
import io.flutter.plugin.common.MethodChannel.Result
28+
import io.flutter.plugin.common.EventChannel
29+
import io.flutter.plugin.common.EventChannel.EventSink
30+
import io.flutter.plugin.common.EventChannel.StreamHandler
2731
import org.json.JSONArray
2832
import org.json.JSONObject
2933
import java.io.IOException
@@ -45,6 +49,16 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
4549

4650
private lateinit var nfcHandlerThread: HandlerThread
4751
private lateinit var nfcHandler: Handler
52+
private lateinit var methodChannel: MethodChannel
53+
private lateinit var eventChannel: EventChannel
54+
private var eventSink: EventSink? = null
55+
56+
public fun handleTag(tag: Tag) {
57+
val result = parseTag(tag)
58+
Handler(Looper.getMainLooper()).post {
59+
eventSink?.success(result)
60+
}
61+
}
4862

4963
private fun TagTechnology.transceive(data: ByteArray, timeout: Int?): ByteArray {
5064
if (timeout != null) {
@@ -79,18 +93,178 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
7993
result.error("500", "Failed to post job to NFC Handler thread.", null)
8094
}
8195
}
96+
97+
private fun parseTag(tag: Tag): String {
98+
// common fields
99+
val type: String
100+
val id = tag.id.toHexString()
101+
val standard: String
102+
// ISO 14443 Type A
103+
var atqa = ""
104+
var sak = ""
105+
// ISO 14443 Type B
106+
var protocolInfo = ""
107+
var applicationData = ""
108+
// ISO 7816
109+
var historicalBytes = ""
110+
var hiLayerResponse = ""
111+
// NFC-F / Felica
112+
var manufacturer = ""
113+
var systemCode = ""
114+
// NFC-V
115+
var dsfId = ""
116+
// NDEF
117+
var ndefAvailable = false
118+
var ndefWritable = false
119+
var ndefCanMakeReadOnly = false
120+
var ndefCapacity = 0
121+
var ndefType = ""
122+
123+
if (tag.techList.contains(NfcA::class.java.name)) {
124+
val aTag = NfcA.get(tag)
125+
atqa = aTag.atqa.toHexString()
126+
sak = byteArrayOf(aTag.sak.toByte()).toHexString()
127+
tagTechnology = aTag
128+
when {
129+
tag.techList.contains(IsoDep::class.java.name) -> {
130+
standard = "ISO 14443-4 (Type A)"
131+
type = "iso7816"
132+
val isoDep = IsoDep.get(tag)
133+
tagTechnology = isoDep
134+
historicalBytes = isoDep.historicalBytes.toHexString()
135+
}
136+
tag.techList.contains(MifareClassic::class.java.name) -> {
137+
standard = "ISO 14443-3 (Type A)"
138+
type = "mifare_classic"
139+
with(MifareClassic.get(tag)) {
140+
tagTechnology = this
141+
mifareInfo = MifareInfo(
142+
this.type,
143+
size,
144+
MifareClassic.BLOCK_SIZE,
145+
blockCount,
146+
sectorCount
147+
)
148+
}
149+
}
150+
tag.techList.contains(MifareUltralight::class.java.name) -> {
151+
standard = "ISO 14443-3 (Type A)"
152+
type = "mifare_ultralight"
153+
with(MifareUltralight.get(tag)) {
154+
tagTechnology = this
155+
mifareInfo = MifareInfo.fromUltralight(this.type)
156+
}
157+
}
158+
else -> {
159+
standard = "ISO 14443-3 (Type A)"
160+
type = "unknown"
161+
}
162+
}
163+
} else if (tag.techList.contains(NfcB::class.java.name)) {
164+
val bTag = NfcB.get(tag)
165+
protocolInfo = bTag.protocolInfo.toHexString()
166+
applicationData = bTag.applicationData.toHexString()
167+
if (tag.techList.contains(IsoDep::class.java.name)) {
168+
type = "iso7816"
169+
standard = "ISO 14443-4 (Type B)"
170+
val isoDep = IsoDep.get(tag)
171+
tagTechnology = isoDep
172+
hiLayerResponse = isoDep.hiLayerResponse.toHexString()
173+
} else {
174+
type = "unknown"
175+
standard = "ISO 14443-3 (Type B)"
176+
tagTechnology = bTag
177+
}
178+
} else if (tag.techList.contains(NfcF::class.java.name)) {
179+
standard = "ISO 18092 (FeliCa)"
180+
type = "iso18092"
181+
val fTag = NfcF.get(tag)
182+
manufacturer = fTag.manufacturer.toHexString()
183+
systemCode = fTag.systemCode.toHexString()
184+
tagTechnology = fTag
185+
} else if (tag.techList.contains(NfcV::class.java.name)) {
186+
standard = "ISO 15693"
187+
type = "iso15693"
188+
val vTag = NfcV.get(tag)
189+
dsfId = vTag.dsfId.toHexString()
190+
tagTechnology = vTag
191+
} else {
192+
type = "unknown"
193+
standard = "unknown"
194+
}
195+
196+
// detect ndef
197+
if (tag.techList.contains(Ndef::class.java.name)) {
198+
val ndefTag = Ndef.get(tag)
199+
ndefTechnology = ndefTag
200+
ndefAvailable = true
201+
ndefType = ndefTag.type
202+
ndefWritable = ndefTag.isWritable
203+
ndefCanMakeReadOnly = ndefTag.canMakeReadOnly()
204+
ndefCapacity = ndefTag.maxSize
205+
}
206+
207+
val jsonResult = JSONObject(mapOf(
208+
"type" to type,
209+
"id" to id,
210+
"standard" to standard,
211+
"atqa" to atqa,
212+
"sak" to sak,
213+
"historicalBytes" to historicalBytes,
214+
"protocolInfo" to protocolInfo,
215+
"applicationData" to applicationData,
216+
"hiLayerResponse" to hiLayerResponse,
217+
"manufacturer" to manufacturer,
218+
"systemCode" to systemCode,
219+
"dsfId" to dsfId,
220+
"ndefAvailable" to ndefAvailable,
221+
"ndefType" to ndefType,
222+
"ndefWritable" to ndefWritable,
223+
"ndefCanMakeReadOnly" to ndefCanMakeReadOnly,
224+
"ndefCapacity" to ndefCapacity,
225+
))
226+
227+
if (mifareInfo != null) {
228+
with(mifareInfo!!) {
229+
jsonResult.put("mifareInfo", JSONObject(mapOf(
230+
"type" to typeStr,
231+
"size" to size,
232+
"blockSize" to blockSize,
233+
"blockCount" to blockCount,
234+
"sectorCount" to sectorCount
235+
)))
236+
}
237+
}
238+
239+
return jsonResult.toString()
240+
}
82241
}
83242

84243
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
85244
nfcHandlerThread = HandlerThread("NfcHandlerThread")
86245
nfcHandlerThread.start()
87246
nfcHandler = Handler(nfcHandlerThread.looper)
88247

89-
val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_nfc_kit")
90-
channel.setMethodCallHandler(this)
248+
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_nfc_kit/method")
249+
methodChannel.setMethodCallHandler(this)
250+
251+
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "flutter_nfc_kit/event")
252+
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
253+
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
254+
if (events != null) {
255+
eventSink = events
256+
}
257+
}
258+
259+
override fun onCancel(arguments: Any?) {
260+
// No need to do anything here
261+
}
262+
})
91263
}
92264

93265
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
266+
methodChannel.setMethodCallHandler(null)
267+
eventChannel.setStreamHandler(null)
94268
nfcHandlerThread.quitSafely()
95269
}
96270

@@ -418,148 +592,8 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
418592
val pollHandler = NfcAdapter.ReaderCallback { tag ->
419593
pollingTimeoutTask?.cancel()
420594

421-
// common fields
422-
val type: String
423-
val id = tag.id.toHexString()
424-
val standard: String
425-
// ISO 14443 Type A
426-
var atqa = ""
427-
var sak = ""
428-
// ISO 14443 Type B
429-
var protocolInfo = ""
430-
var applicationData = ""
431-
// ISO 7816
432-
var historicalBytes = ""
433-
var hiLayerResponse = ""
434-
// NFC-F / Felica
435-
var manufacturer = ""
436-
var systemCode = ""
437-
// NFC-V
438-
var dsfId = ""
439-
// NDEF
440-
var ndefAvailable = false
441-
var ndefWritable = false
442-
var ndefCanMakeReadOnly = false
443-
var ndefCapacity = 0
444-
var ndefType = ""
445-
446-
if (tag.techList.contains(NfcA::class.java.name)) {
447-
val aTag = NfcA.get(tag)
448-
atqa = aTag.atqa.toHexString()
449-
sak = byteArrayOf(aTag.sak.toByte()).toHexString()
450-
tagTechnology = aTag
451-
when {
452-
tag.techList.contains(IsoDep::class.java.name) -> {
453-
standard = "ISO 14443-4 (Type A)"
454-
type = "iso7816"
455-
val isoDep = IsoDep.get(tag)
456-
tagTechnology = isoDep
457-
historicalBytes = isoDep.historicalBytes.toHexString()
458-
}
459-
tag.techList.contains(MifareClassic::class.java.name) -> {
460-
standard = "ISO 14443-3 (Type A)"
461-
type = "mifare_classic"
462-
with(MifareClassic.get(tag)) {
463-
tagTechnology = this
464-
mifareInfo = MifareInfo(
465-
this.type,
466-
size,
467-
MifareClassic.BLOCK_SIZE,
468-
blockCount,
469-
sectorCount
470-
)
471-
}
472-
}
473-
tag.techList.contains(MifareUltralight::class.java.name) -> {
474-
standard = "ISO 14443-3 (Type A)"
475-
type = "mifare_ultralight"
476-
with(MifareUltralight.get(tag)) {
477-
tagTechnology = this
478-
mifareInfo = MifareInfo.fromUltralight(this.type)
479-
}
480-
}
481-
else -> {
482-
standard = "ISO 14443-3 (Type A)"
483-
type = "unknown"
484-
}
485-
}
486-
} else if (tag.techList.contains(NfcB::class.java.name)) {
487-
val bTag = NfcB.get(tag)
488-
protocolInfo = bTag.protocolInfo.toHexString()
489-
applicationData = bTag.applicationData.toHexString()
490-
if (tag.techList.contains(IsoDep::class.java.name)) {
491-
type = "iso7816"
492-
standard = "ISO 14443-4 (Type B)"
493-
val isoDep = IsoDep.get(tag)
494-
tagTechnology = isoDep
495-
hiLayerResponse = isoDep.hiLayerResponse.toHexString()
496-
} else {
497-
type = "unknown"
498-
standard = "ISO 14443-3 (Type B)"
499-
tagTechnology = bTag
500-
}
501-
} else if (tag.techList.contains(NfcF::class.java.name)) {
502-
standard = "ISO 18092 (FeliCa)"
503-
type = "iso18092"
504-
val fTag = NfcF.get(tag)
505-
manufacturer = fTag.manufacturer.toHexString()
506-
systemCode = fTag.systemCode.toHexString()
507-
tagTechnology = fTag
508-
} else if (tag.techList.contains(NfcV::class.java.name)) {
509-
standard = "ISO 15693"
510-
type = "iso15693"
511-
val vTag = NfcV.get(tag)
512-
dsfId = vTag.dsfId.toHexString()
513-
tagTechnology = vTag
514-
} else {
515-
type = "unknown"
516-
standard = "unknown"
517-
}
518-
519-
// detect ndef
520-
if (tag.techList.contains(Ndef::class.java.name)) {
521-
val ndefTag = Ndef.get(tag)
522-
ndefTechnology = ndefTag
523-
ndefAvailable = true
524-
ndefType = ndefTag.type
525-
ndefWritable = ndefTag.isWritable
526-
ndefCanMakeReadOnly = ndefTag.canMakeReadOnly()
527-
ndefCapacity = ndefTag.maxSize
528-
}
529-
530-
val jsonResult = JSONObject(mapOf(
531-
"type" to type,
532-
"id" to id,
533-
"standard" to standard,
534-
"atqa" to atqa,
535-
"sak" to sak,
536-
"historicalBytes" to historicalBytes,
537-
"protocolInfo" to protocolInfo,
538-
"applicationData" to applicationData,
539-
"hiLayerResponse" to hiLayerResponse,
540-
"manufacturer" to manufacturer,
541-
"systemCode" to systemCode,
542-
"dsfId" to dsfId,
543-
"ndefAvailable" to ndefAvailable,
544-
"ndefType" to ndefType,
545-
"ndefWritable" to ndefWritable,
546-
"ndefCanMakeReadOnly" to ndefCanMakeReadOnly,
547-
"ndefCapacity" to ndefCapacity,
548-
))
549-
550-
if (mifareInfo != null) {
551-
with(mifareInfo!!) {
552-
jsonResult.put("mifareInfo", JSONObject(mapOf(
553-
"type" to typeStr,
554-
"size" to size,
555-
"blockSize" to blockSize,
556-
"blockCount" to blockCount,
557-
"sectorCount" to sectorCount
558-
)))
559-
}
560-
}
561-
562-
result.success(jsonResult.toString())
595+
val jsonResult = parseTag(tag)
596+
result.success(jsonResult)
563597
}
564598

565599
nfcAdapter.enableReaderMode(activity.get(), pollHandler, technologies, null)

example/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
/build/
3232

3333
# Web related
34-
lib/generated_plugin_registrant.dart
3534

3635
# Exceptions to above rules.
3736
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

0 commit comments

Comments
 (0)