From 2dbab2520ddf29d9c12281382c11217321806125 Mon Sep 17 00:00:00 2001 From: "Kenneth J. Miller" Date: Wed, 29 May 2024 15:16:54 +0200 Subject: [PATCH] feat: implement dedicated handler thread for IO operations Previously, a new thread would be spawned for each IO operation, this would in some cases lead to race conditions as IO operations could overlap. Fixes #166 --- .../flutter_nfc_kit/FlutterNfcKitPlugin.kt | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt b/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt index d427af9..11e2a19 100644 --- a/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt +++ b/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt @@ -8,6 +8,7 @@ import android.nfc.NfcAdapter import android.nfc.NfcAdapter.* import android.nfc.tech.* import android.os.Handler +import android.os.HandlerThread import android.os.Looper import im.nfc.flutter_nfc_kit.ByteUtils.canonicalizeData import im.nfc.flutter_nfc_kit.ByteUtils.hexToBytes @@ -30,7 +31,6 @@ import java.lang.ref.WeakReference import java.lang.reflect.InvocationTargetException import java.util.* import kotlin.concurrent.schedule -import kotlin.concurrent.thread class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { @@ -43,6 +43,9 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private var ndefTechnology: Ndef? = null private var mifareInfo: MifareInfo? = null + private lateinit var nfcHandlerThread: HandlerThread + private lateinit var nfcHandler: Handler + private fun TagTechnology.transceive(data: ByteArray, timeout: Int?): ByteArray { if (timeout != null) { try { @@ -56,10 +59,18 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + nfcHandlerThread = HandlerThread("NfcHandlerThread") + nfcHandlerThread.start() + nfcHandler = Handler(nfcHandlerThread.looper) + val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_nfc_kit") channel.setMethodCallHandler(this) } + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + nfcHandlerThread.quitSafely() + } + override fun onMethodCall(call: MethodCall, result: Result) { handleMethodCall(call, MethodResultWrapper(result)) } @@ -113,14 +124,14 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { val timeout = call.argument("timeout")!! // technology and option bits are set in Dart code val technologies = call.argument("technologies")!! - thread { + nfcHandler.post { pollTag(nfcAdapter, result, timeout, technologies) } } "finish" -> { pollingTimeoutTask?.cancel() - thread { + nfcHandler.post { try { val tagTech = tagTechnology if (tagTech != null && tagTech.isConnected) { @@ -155,7 +166,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } val (sendingBytes, sendingHex) = canonicalizeData(data) - thread { + nfcHandler.post { try { switchTechnology(tagTech, ndefTechnology) val timeout = call.argument("timeout") @@ -187,7 +198,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "readNDEF" -> { if (!ensureNDEF()) return val ndef = ndefTechnology!! - thread { + nfcHandler.post { try { switchTechnology(ndef, tagTechnology) // read NDEF message @@ -236,7 +247,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("405", "Tag not writable", null) return } - thread { + nfcHandler.post { try { switchTechnology(ndef, tagTechnology) // generate NDEF message @@ -283,7 +294,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("405", "Tag not writable", null) return } - thread { + nfcHandler.post { try { switchTechnology(ndef, tagTechnology) if (ndef.makeReadOnly()) { @@ -316,22 +327,22 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } val keyA = call.argument("keyA") val keyB = call.argument("keyB") - thread { + nfcHandler.post { try { val tag = tagTech as MifareClassic switchTechnology(tagTech, ndefTechnology) // key A takes precedence if present - val success = if (keyA != null) { + if (keyA != null) { val (key, _) = canonicalizeData(keyA) - tag.authenticateSectorWithKeyA(index, key) + val authStatus = tag.authenticateSectorWithKeyA(index, key) + result.success(authStatus) } else if (keyB != null) { val (key, _) = canonicalizeData(keyB) - tag.authenticateSectorWithKeyB(index, key) + val authStatus = tag.authenticateSectorWithKeyB(index, key) + result.success(authStatus) } else { result.error("400", "No keys provided", null) - return@thread } - result.success(success) } catch (ex: IOException) { Log.e(TAG, "Authenticate block error", ex) result.error("500", "Authentication error", ex.localizedMessage) @@ -351,7 +362,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid block/page index $index, should be in (0, $maxBlock)", null) return } - thread { + nfcHandler.post { try { switchTechnology(tagTech, ndefTechnology) tagTech.readBlock(index, result) @@ -374,7 +385,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid sector index $index, should be in (0, $maxSector)", null) return } - thread { + nfcHandler.post { try { val tag = tagTech as MifareClassic switchTechnology(tagTech, ndefTechnology) @@ -407,7 +418,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid data size ${bytes.size}, should be ${mifareInfo!!.blockSize}", null) return } - thread { + nfcHandler.post { try { switchTechnology(tagTech, ndefTechnology) tagTech.writeBlock(index, bytes, result) @@ -421,9 +432,6 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} - override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = WeakReference(binding.activity) }