From 18e4684a9e7039e475f009537c6b9ecea679e091 Mon Sep 17 00:00:00 2001 From: "Kenneth J. Miller" Date: Wed, 29 May 2024 17:48:34 +0200 Subject: [PATCH] refactor: centralize Android NFC method exception handling Fixes #168 --- .../flutter_nfc_kit/FlutterNfcKitPlugin.kt | 297 +++++++----------- 1 file changed, 119 insertions(+), 178 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 a78285a..4140871 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 @@ -57,8 +57,25 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { return transceiveMethod.invoke(this, data) as ByteArray } - private fun runOnNfcThread(result: Result, fn: () -> Unit) { - if (!nfcHandler.post(fn)) { + private fun runOnNfcThread(result: Result, desc: String, fn: () -> Unit) { + val handledFn = Runnable { + try { + fn() + } catch (ex: Exception) { + Log.e(TAG, "$desc error", ex) + val excMessage = ex.localizedMessage + when (ex) { + is IOException -> result?.error("500", "Communication error", excMessage) + is SecurityException -> result?.error("503", "Tag already removed", excMessage) + is FormatException -> result?.error("400", "NDEF format error", excMessage) + is InvocationTargetException -> result?.error("500", "Communication error", excMessage) + is IllegalArgumentException -> result?.error("400", "Command format error", excMessage) + is NoSuchMethodException -> result?.error("405", "Transceive not supported for this type of card", excMessage) + else -> result?.error("500", "Unhandled error", excMessage) + } + } + } + if (!nfcHandler.post(handledFn)) { result.error("500", "Failed to post job to NFC Handler thread.", null) } } @@ -130,27 +147,21 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { val timeout = call.argument("timeout")!! // technology and option bits are set in Dart code val technologies = call.argument("technologies")!! - runOnNfcThread(result) { + runOnNfcThread(result, "Poll") { pollTag(nfcAdapter, result, timeout, technologies) } } "finish" -> { pollingTimeoutTask?.cancel() - runOnNfcThread(result) { - try { - val tagTech = tagTechnology - if (tagTech != null && tagTech.isConnected) { - tagTech.close() - } - val ndefTech = ndefTechnology - if (ndefTech != null && ndefTech.isConnected) { - ndefTech.close() - } - } catch (ex: SecurityException) { - Log.e(TAG, "Tag already removed", ex) - } catch (ex: IOException) { - Log.e(TAG, "Close tag error", ex) + runOnNfcThread(result, "Close tag") { + val tagTech = tagTechnology + if (tagTech != null && tagTech.isConnected) { + tagTech.close() + } + val ndefTech = ndefTechnology + if (ndefTech != null && ndefTech.isConnected) { + ndefTech.close() } if (activity.get() != null) { nfcAdapter.disableReaderMode(activity.get()) @@ -172,30 +183,13 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } val (sendingBytes, sendingHex) = canonicalizeData(data) - runOnNfcThread(result) { - try { - switchTechnology(tagTech, ndefTechnology) - val timeout = call.argument("timeout") - val resp = tagTech.transceive(sendingBytes, timeout) - when (data) { - is String -> result.success(resp.toHexString()) - else -> result.success(resp) - } - } catch (ex: SecurityException) { - Log.e(TAG, "Transceive Error: $sendingHex", ex) - result.error("503", "Tag already removed", ex.localizedMessage) - } catch (ex: IOException) { - Log.e(TAG, "Transceive Error: $sendingHex", ex) - result.error("500", "Communication error", ex.localizedMessage) - } catch (ex: InvocationTargetException) { - Log.e(TAG, "Transceive Error: $sendingHex", ex.cause ?: ex) - result.error("500", "Communication error", ex.cause?.localizedMessage) - } catch (ex: IllegalArgumentException) { - Log.e(TAG, "Command Error: $sendingHex", ex) - result.error("400", "Command format error", ex.localizedMessage) - } catch (ex: NoSuchMethodException) { - Log.e(TAG, "Transceive not supported: $sendingHex", ex) - result.error("405", "Transceive not supported for this type of card", null) + runOnNfcThread(result, "Transceive: $sendingHex") { + switchTechnology(tagTech, ndefTechnology) + val timeout = call.argument("timeout") + val resp = tagTech.transceive(sendingBytes, timeout) + when (data) { + is String -> result.success(resp.toHexString()) + else -> result.success(resp) } } } @@ -204,45 +198,34 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { "readNDEF" -> { if (!ensureNDEF()) return val ndef = ndefTechnology!! - runOnNfcThread(result) { - try { - switchTechnology(ndef, tagTechnology) - // read NDEF message - val message: NdefMessage? = if (call.argument("cached")!!) { - ndef.cachedNdefMessage - } else { - ndef.ndefMessage - } - val parsedMessages = mutableListOf>() - if (message != null) { - for (record in message.records) { - parsedMessages.add(mapOf( - "identifier" to record.id.toHexString(), - "payload" to record.payload.toHexString(), - "type" to record.type.toHexString(), - "typeNameFormat" to when (record.tnf) { - NdefRecord.TNF_ABSOLUTE_URI -> "absoluteURI" - NdefRecord.TNF_EMPTY -> "empty" - NdefRecord.TNF_EXTERNAL_TYPE -> "nfcExternal" - NdefRecord.TNF_WELL_KNOWN -> "nfcWellKnown" - NdefRecord.TNF_MIME_MEDIA -> "media" - NdefRecord.TNF_UNCHANGED -> "unchanged" - else -> "unknown" - } - )) - } + runOnNfcThread(result, "Read NDEF") { + switchTechnology(ndef, tagTechnology) + // read NDEF message + val message: NdefMessage? = if (call.argument("cached")!!) { + ndef.cachedNdefMessage + } else { + ndef.ndefMessage + } + val parsedMessages = mutableListOf>() + if (message != null) { + for (record in message.records) { + parsedMessages.add(mapOf( + "identifier" to record.id.toHexString(), + "payload" to record.payload.toHexString(), + "type" to record.type.toHexString(), + "typeNameFormat" to when (record.tnf) { + NdefRecord.TNF_ABSOLUTE_URI -> "absoluteURI" + NdefRecord.TNF_EMPTY -> "empty" + NdefRecord.TNF_EXTERNAL_TYPE -> "nfcExternal" + NdefRecord.TNF_WELL_KNOWN -> "nfcWellKnown" + NdefRecord.TNF_MIME_MEDIA -> "media" + NdefRecord.TNF_UNCHANGED -> "unchanged" + else -> "unknown" + } + )) } - result.success(JSONArray(parsedMessages).toString()) - } catch (ex: SecurityException) { - Log.e(TAG, "Read NDEF Error", ex) - result.error("503", "Tag already removed", ex.localizedMessage) - } catch (ex: IOException) { - Log.e(TAG, "Read NDEF Error", ex) - result.error("500", "Communication error", ex.localizedMessage) - } catch (ex: FormatException) { - Log.e(TAG, "NDEF Format Error", ex) - result.error("400", "NDEF format error", ex.localizedMessage) } + result.success(JSONArray(parsedMessages).toString()) } } @@ -253,43 +236,32 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("405", "Tag not writable", null) return } - runOnNfcThread(result) { - try { - switchTechnology(ndef, tagTechnology) - // generate NDEF message - val jsonString = call.argument("data")!! - val recordData = JSONArray(jsonString) - val records = Array(recordData.length(), init = { i: Int -> - val record: JSONObject = recordData.get(i) as JSONObject - NdefRecord( - when (record.getString("typeNameFormat")) { - "absoluteURI" -> NdefRecord.TNF_ABSOLUTE_URI - "empty" -> NdefRecord.TNF_EMPTY - "nfcExternal" -> NdefRecord.TNF_EXTERNAL_TYPE - "nfcWellKnown" -> NdefRecord.TNF_WELL_KNOWN - "media" -> NdefRecord.TNF_MIME_MEDIA - "unchanged" -> NdefRecord.TNF_UNCHANGED - else -> NdefRecord.TNF_UNKNOWN - }, - record.getString("type").hexToBytes(), - record.getString("identifier").hexToBytes(), - record.getString("payload").hexToBytes() - ) - }) - // write NDEF message - val message = NdefMessage(records) - ndef.writeNdefMessage(message) - result.success("") - } catch (ex: SecurityException) { - Log.e(TAG, "Write NDEF Error", ex) - result.error("503", "Tag already removed", ex.localizedMessage) - } catch (ex: IOException) { - Log.e(TAG, "Write NDEF Error", ex) - result.error("500", "Communication error", ex.localizedMessage) - } catch (ex: FormatException) { - Log.e(TAG, "NDEF Format Error", ex) - result.error("400", "NDEF format error", ex.localizedMessage) - } + runOnNfcThread(result, "Write NDEF") { + switchTechnology(ndef, tagTechnology) + // generate NDEF message + val jsonString = call.argument("data")!! + val recordData = JSONArray(jsonString) + val records = Array(recordData.length(), init = { i: Int -> + val record: JSONObject = recordData.get(i) as JSONObject + NdefRecord( + when (record.getString("typeNameFormat")) { + "absoluteURI" -> NdefRecord.TNF_ABSOLUTE_URI + "empty" -> NdefRecord.TNF_EMPTY + "nfcExternal" -> NdefRecord.TNF_EXTERNAL_TYPE + "nfcWellKnown" -> NdefRecord.TNF_WELL_KNOWN + "media" -> NdefRecord.TNF_MIME_MEDIA + "unchanged" -> NdefRecord.TNF_UNCHANGED + else -> NdefRecord.TNF_UNKNOWN + }, + record.getString("type").hexToBytes(), + record.getString("identifier").hexToBytes(), + record.getString("payload").hexToBytes() + ) + }) + // write NDEF message + val message = NdefMessage(records) + ndef.writeNdefMessage(message) + result.success("") } } @@ -300,20 +272,12 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("405", "Tag not writable", null) return } - runOnNfcThread(result) { - try { - switchTechnology(ndef, tagTechnology) - if (ndef.makeReadOnly()) { - result.success("") - } else { - result.error("500", "Failed to lock NDEF tag", null) - } - } catch (ex: SecurityException) { - Log.e(TAG, "Lock NDEF Error", ex) - result.error("503", "Tag already removed", ex.localizedMessage) - } catch (ex: IOException) { - Log.e(TAG, "Lock NDEF Error", ex) - result.error("500", "Communication error", ex.localizedMessage) + runOnNfcThread(result, "Lock NDEF") { + switchTechnology(ndef, tagTechnology) + if (ndef.makeReadOnly()) { + result.success("") + } else { + result.error("500", "Failed to lock NDEF tag", null) } } } @@ -333,25 +297,20 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } val keyA = call.argument("keyA") val keyB = call.argument("keyB") - runOnNfcThread(result) { - try { - val tag = tagTech as MifareClassic - switchTechnology(tagTech, ndefTechnology) - // key A takes precedence if present - if (keyA != null) { - val (key, _) = canonicalizeData(keyA) - val authStatus = tag.authenticateSectorWithKeyA(index, key) - result.success(authStatus) - } else if (keyB != null) { - val (key, _) = canonicalizeData(keyB) - val authStatus = tag.authenticateSectorWithKeyB(index, key) - result.success(authStatus) - } else { - result.error("400", "No keys provided", null) - } - } catch (ex: IOException) { - Log.e(TAG, "Authenticate block error", ex) - result.error("500", "Authentication error", ex.localizedMessage) + runOnNfcThread(result, "Authenticate sector") { + val tag = tagTech as MifareClassic + switchTechnology(tagTech, ndefTechnology) + // key A takes precedence if present + if (keyA != null) { + val (key, _) = canonicalizeData(keyA) + val authStatus = tag.authenticateSectorWithKeyA(index, key) + result.success(authStatus) + } else if (keyB != null) { + val (key, _) = canonicalizeData(keyB) + val authStatus = tag.authenticateSectorWithKeyB(index, key) + result.success(authStatus) + } else { + result.error("400", "No keys provided", null) } } } @@ -368,14 +327,9 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid block/page index $index, should be in (0, $maxBlock)", null) return } - runOnNfcThread(result) { - try { - switchTechnology(tagTech, ndefTechnology) - tagTech.readBlock(index, result) - } catch (ex: IOException) { - Log.e(TAG, "Read block error", ex) - result.error("500", "Communication error", ex.localizedMessage) - } + runOnNfcThread(result, "Read block") { + switchTechnology(tagTech, ndefTechnology) + tagTech.readBlock(index, result) } } @@ -391,14 +345,10 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid sector index $index, should be in (0, $maxSector)", null) return } - runOnNfcThread(result) { - try { - val tag = tagTech as MifareClassic - switchTechnology(tagTech, ndefTechnology) - result.success(tag.readSector(index)) - } catch (ex: IOException) { - Log.e(TAG, "Read sector error", ex) - result.error("500", "Communication error", ex.localizedMessage) } + runOnNfcThread(result, "Read sector") { + val tag = tagTech as MifareClassic + switchTechnology(tagTech, ndefTechnology) + result.success(tag.readSector(index)) } } @@ -424,14 +374,9 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.error("400", "Invalid data size ${bytes.size}, should be ${mifareInfo!!.blockSize}", null) return } - runOnNfcThread(result) { - try { - switchTechnology(tagTech, ndefTechnology) - tagTech.writeBlock(index, bytes, result) - } catch (ex: IOException) { - Log.e(TAG, "Read block error", ex) - result.error("500", "Communication error", ex.localizedMessage) - } + runOnNfcThread(result, "Write block") { + switchTechnology(tagTech, ndefTechnology) + tagTech.writeBlock(index, bytes, result) } } @@ -459,6 +404,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { pollingTimeoutTask = Timer().schedule(timeout.toLong()) { try { if (activity.get() != null) { + nfcAdapter.disableReaderMode(activity.get()) } } catch (ex: Exception) { @@ -614,12 +560,7 @@ class FlutterNfcKitPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { result.success(jsonResult.toString()) } - try { - nfcAdapter.enableReaderMode(activity.get(), pollHandler, technologies, null) - } catch (ex: Exception) { - Log.e(TAG, "Cannot enable reader mode", ex) - result.error("500", "Cannot enable reader mode", ex.localizedMessage) - } + nfcAdapter.enableReaderMode(activity.get(), pollHandler, technologies, null) } private class MethodResultWrapper(result: Result) : Result {