diff --git a/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java b/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java index 07e662ba..c2cf950d 100644 --- a/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java +++ b/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java @@ -1,5 +1,20 @@ package com.chariotsolutions.nfc.plugin; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +// using wildcard imports so we can support Cordova 3.x +import org.apache.cordova.*; // Cordova 3.x + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; @@ -15,24 +30,11 @@ import android.nfc.TagLostException; import android.nfc.tech.Ndef; import android.nfc.tech.NdefFormatable; +import android.nfc.tech.TagTechnology; +import android.os.Bundle; import android.os.Parcelable; import android.util.Log; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -// using wildcard imports so we can support Cordova 3.x - public class NfcPlugin extends CordovaPlugin implements NfcAdapter.OnNdefPushCompleteCallback { private static final String REGISTER_MIME_TYPE = "registerMimeType"; private static final String REMOVE_MIME_TYPE = "removeMimeType"; @@ -57,20 +59,34 @@ public class NfcPlugin extends CordovaPlugin implements NfcAdapter.OnNdefPushCom private static final String NDEF_FORMATABLE = "ndef-formatable"; private static final String TAG_DEFAULT = "tag"; + private static final String READER_MODE = "readerMode"; + private static final String DISABLE_READER_MODE = "disableReaderMode"; + + // TagTechnology IsoDep, NfcA, NfcB, NfcV, NfcF, MifareClassic, MifareUltralight + private static final String CONNECT = "connect"; + private static final String CLOSE = "close"; + private static final String TRANSCEIVE = "transceive"; + private TagTechnology tagTechnology = null; + private Class tagTechnologyClass; + + private static final String CHANNEL = "channel"; + private static final String STATUS_NFC_OK = "NFC_OK"; private static final String STATUS_NO_NFC = "NO_NFC"; private static final String STATUS_NFC_DISABLED = "NFC_DISABLED"; private static final String STATUS_NDEF_PUSH_DISABLED = "NDEF_PUSH_DISABLED"; private static final String TAG = "NfcPlugin"; - private final List intentFilters = new ArrayList(); - private final ArrayList techLists = new ArrayList(); + private final List intentFilters = new ArrayList<>(); + private final ArrayList techLists = new ArrayList<>(); private NdefMessage p2pMessage = null; private PendingIntent pendingIntent = null; private Intent savedIntent = null; + private CallbackContext readerModeCallback; + private CallbackContext channelCallback; private CallbackContext shareTagCallback; private CallbackContext handoverCallback; @@ -86,6 +102,18 @@ public boolean execute(String action, JSONArray data, CallbackContext callbackCo return true; } + // the channel is set up when the plugin starts + if (action.equalsIgnoreCase(CHANNEL)) { + channelCallback = callbackContext; + return true; // short circuit + } + + // allow reader mode to be disabled even if nfc is disabled + if (action.equalsIgnoreCase(DISABLE_READER_MODE)) { + disableReaderMode(callbackContext); + return true; // short circuit + } + if (!getNfcStatus().equals(STATUS_NFC_OK)) { callbackContext.error(getNfcStatus()); return true; // short circuit @@ -93,26 +121,30 @@ public boolean execute(String action, JSONArray data, CallbackContext callbackCo createPendingIntent(); - if (action.equalsIgnoreCase(REGISTER_MIME_TYPE)) { + if (action.equalsIgnoreCase(READER_MODE)) { + int flags = data.getInt(0); + readerMode(flags, callbackContext); + + } else if (action.equalsIgnoreCase(REGISTER_MIME_TYPE)) { registerMimeType(data, callbackContext); } else if (action.equalsIgnoreCase(REMOVE_MIME_TYPE)) { - removeMimeType(data, callbackContext); + removeMimeType(data, callbackContext); } else if (action.equalsIgnoreCase(REGISTER_NDEF)) { - registerNdef(callbackContext); + registerNdef(callbackContext); } else if (action.equalsIgnoreCase(REMOVE_NDEF)) { - removeNdef(callbackContext); + removeNdef(callbackContext); } else if (action.equalsIgnoreCase(REGISTER_NDEF_FORMATABLE)) { registerNdefFormatable(callbackContext); - } else if (action.equals(REGISTER_DEFAULT_TAG)) { - registerDefaultTag(callbackContext); + } else if (action.equals(REGISTER_DEFAULT_TAG)) { + registerDefaultTag(callbackContext); - } else if (action.equals(REMOVE_DEFAULT_TAG)) { - removeDefaultTag(callbackContext); + } else if (action.equals(REMOVE_DEFAULT_TAG)) { + removeDefaultTag(callbackContext); } else if (action.equalsIgnoreCase(WRITE_TAG)) { writeTag(data, callbackContext); @@ -143,6 +175,20 @@ public boolean execute(String action, JSONArray data, CallbackContext callbackCo // if code made it here, NFC is enabled callbackContext.success(STATUS_NFC_OK); + } else if (action.equalsIgnoreCase(CONNECT)) { + String tech = data.getString(0); + int timeout = data.optInt(1, -1); + connect(tech, timeout, callbackContext); + + } else if (action.equalsIgnoreCase(TRANSCEIVE)) { + CordovaArgs args = new CordovaArgs(data); // execute is using the old signature with JSON data + + byte[] command = args.getArrayBuffer(0); + transceive(command, callbackContext); + + } else if (action.equalsIgnoreCase(CLOSE)) { + close(callbackContext); + } else { // invalid action return false; @@ -162,30 +208,86 @@ private String getNfcStatus() { } } + private void readerMode(int flags, CallbackContext callbackContext) { + Bundle extras = new Bundle(); // not used + readerModeCallback = callbackContext; + getActivity().runOnUiThread(() -> { + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + nfcAdapter.enableReaderMode(getActivity(), callback, flags, extras); + }); + + } + + private void disableReaderMode(CallbackContext callbackContext) { + getActivity().runOnUiThread(() -> { + readerModeCallback = null; + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (nfcAdapter != null) { + nfcAdapter.disableReaderMode(getActivity()); + } + callbackContext.success(); + }); + } + + private NfcAdapter.ReaderCallback callback = new NfcAdapter.ReaderCallback() { + @Override + public void onTagDiscovered(Tag tag) { + + JSONObject json; + + // If the tag supports Ndef, try and return an Ndef message + List techList = Arrays.asList(tag.getTechList()); + if (techList.contains(Ndef.class.getName())) { + Ndef ndef = Ndef.get(tag); + json = Util.ndefToJSON(ndef); + } else { + json = Util.tagToJSON(tag); + } + + Intent tagIntent = new Intent(); + tagIntent.putExtra(NfcAdapter.EXTRA_TAG, tag); + setIntent(tagIntent); + + PluginResult result = new PluginResult(PluginResult.Status.OK, json); + result.setKeepCallback(true); + if (readerModeCallback != null) { + readerModeCallback.sendPluginResult(result); + } else { + Log.i(TAG, "readerModeCallback is null - reader mode probably disabled in the meantime"); + } + + } + }; + private void registerDefaultTag(CallbackContext callbackContext) { - addTagFilter(); - callbackContext.success(); - } + addTagFilter(); + restartNfc(); + callbackContext.success(); + } private void removeDefaultTag(CallbackContext callbackContext) { - removeTagFilter(); - callbackContext.success(); - } + removeTagFilter(); + restartNfc(); + callbackContext.success(); + } private void registerNdefFormatable(CallbackContext callbackContext) { addTechList(new String[]{NdefFormatable.class.getName()}); + restartNfc(); callbackContext.success(); } private void registerNdef(CallbackContext callbackContext) { - addTechList(new String[]{Ndef.class.getName()}); - callbackContext.success(); - } + addTechList(new String[]{Ndef.class.getName()}); + restartNfc(); + callbackContext.success(); + } private void removeNdef(CallbackContext callbackContext) { - removeTechList(new String[]{Ndef.class.getName()}); - callbackContext.success(); - } + removeTechList(new String[]{Ndef.class.getName()}); + restartNfc(); + callbackContext.success(); + } private void unshareTag(CallbackContext callbackContext) { p2pMessage = null; @@ -205,14 +307,10 @@ private void init(CallbackContext callbackContext) { } private void removeMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException { - String mimeType = ""; - try { - mimeType = data.getString(0); - /*boolean removed =*/ removeIntentFilter(mimeType); - callbackContext.success(); - } catch (MalformedMimeTypeException e) { - callbackContext.error("Invalid MIME Type " + mimeType); - } + String mimeType = data.getString(0); + removeIntentFilter(mimeType); + restartNfc(); + callbackContext.success(); } private void registerMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException { @@ -220,6 +318,7 @@ private void registerMimeType(JSONArray data, CallbackContext callbackContext) t try { mimeType = data.getString(0); intentFilters.add(createIntentFilter(mimeType)); + restartNfc(); callbackContext.success(); } catch (MalformedMimeTypeException e) { callbackContext.error("Invalid MIME Type " + mimeType); @@ -227,10 +326,10 @@ private void registerMimeType(JSONArray data, CallbackContext callbackContext) t } // Cheating and writing an empty record. We may actually be able to erase some tag types. - private void eraseTag(CallbackContext callbackContext) throws JSONException { + private void eraseTag(CallbackContext callbackContext) { Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG); NdefRecord[] records = { - new NdefRecord(NdefRecord.TNF_EMPTY, new byte[0], new byte[0], new byte[0]) + new NdefRecord(NdefRecord.TNF_EMPTY, new byte[0], new byte[0], new byte[0]) }; writeNdefMessage(new NdefMessage(records), tag, callbackContext); } @@ -238,7 +337,6 @@ private void eraseTag(CallbackContext callbackContext) throws JSONException { private void writeTag(JSONArray data, CallbackContext callbackContext) throws JSONException { if (getIntent() == null) { // TODO remove this and handle LostTag callbackContext.error("Failed to write tag, received null intent"); - return; } Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG); @@ -247,53 +345,47 @@ private void writeTag(JSONArray data, CallbackContext callbackContext) throws JS } private void writeNdefMessage(final NdefMessage message, final Tag tag, final CallbackContext callbackContext) { - cordova.getThreadPool().execute(new Runnable() { - @Override - public void run() { - try { - Ndef ndef = Ndef.get(tag); - if (ndef != null && !ndef.isConnected()) { - ndef.connect(); - - if (ndef.isWritable()) { - int size = message.toByteArray().length; - if (ndef.getMaxSize() < size) { - callbackContext.error("Tag capacity is " + ndef.getMaxSize() + - " bytes, message is " + size + " bytes."); - } else { - ndef.writeNdefMessage(message); - callbackContext.success(); - } + cordova.getThreadPool().execute(() -> { + try { + Ndef ndef = Ndef.get(tag); + if (ndef != null) { + ndef.connect(); + + if (ndef.isWritable()) { + int size = message.toByteArray().length; + if (ndef.getMaxSize() < size) { + callbackContext.error("Tag capacity is " + ndef.getMaxSize() + + " bytes, message is " + size + " bytes."); } else { - callbackContext.error("Tag is read only"); - } - ndef.close(); - } else { - NdefFormatable formatable = NdefFormatable.get(tag); - if (formatable != null) { - formatable.connect(); - formatable.format(message); + ndef.writeNdefMessage(message); callbackContext.success(); - formatable.close(); - } else { - callbackContext.error("Tag doesn't support NDEF"); } + } else { + callbackContext.error("Tag is read only"); + } + ndef.close(); + } else { + NdefFormatable formatable = NdefFormatable.get(tag); + if (formatable != null) { + formatable.connect(); + formatable.format(message); + callbackContext.success(); + formatable.close(); + } else { + callbackContext.error("Tag doesn't support NDEF"); } - } catch (FormatException e) { - callbackContext.error(e.getMessage()); - } catch (TagLostException e) { - callbackContext.error(e.getMessage()); - } catch (IOException e) { - callbackContext.error(e.getMessage()); - } catch (IllegalStateException e) { - callbackContext.error("Still writing!"); - Log.e(TAG, "Illegal State, still writing !"); } + } catch (FormatException e) { + callbackContext.error(e.getMessage()); + } catch (TagLostException e) { + callbackContext.error(e.getMessage()); + } catch (IOException e) { + callbackContext.error(e.getMessage()); } }); } - private void makeReadOnly(final CallbackContext callbackContext) throws JSONException { + private void makeReadOnly(final CallbackContext callbackContext) { if (getIntent() == null) { // Lost Tag callbackContext.error("Failed to make tag read only, received null intent"); @@ -306,46 +398,43 @@ private void makeReadOnly(final CallbackContext callbackContext) throws JSONExce return; } - cordova.getThreadPool().execute(new Runnable() { - @Override - public void run() { - boolean success = false; - String message = "Could not make tag read only"; + cordova.getThreadPool().execute(() -> { + boolean success = false; + String message = "Could not make tag read only"; - Ndef ndef = Ndef.get(tag); - - try { - if (ndef != null) { + Ndef ndef = Ndef.get(tag); - ndef.connect(); + try { + if (ndef != null) { - if (!ndef.isWritable()) { - message = "Tag is not writable"; - } else if (ndef.canMakeReadOnly()) { - success = ndef.makeReadOnly(); - } else { - message = "Tag can not be made read only"; - } + ndef.connect(); + if (!ndef.isWritable()) { + message = "Tag is not writable"; + } else if (ndef.canMakeReadOnly()) { + success = ndef.makeReadOnly(); } else { - message = "Tag is not NDEF"; + message = "Tag can not be made read only"; } - } catch (IOException e) { - Log.e(TAG, "Failed to make tag read only", e); - if (e.getMessage() != null) { - message = e.getMessage(); - } else { - message = e.toString(); - } + } else { + message = "Tag is not NDEF"; } - if (success) { - callbackContext.success(); + } catch (IOException e) { + Log.e(TAG, "Failed to make tag read only", e); + if (e.getMessage() != null) { + message = e.getMessage(); } else { - callbackContext.error(message); + message = e.toString(); } } + + if (success) { + callbackContext.success(); + } else { + callbackContext.error(message); + } }); } @@ -372,7 +461,7 @@ private void handover(JSONArray data, CallbackContext callbackContext) throws JS startNdefBeam(callbackContext, uri); } - private void stopHandover(CallbackContext callbackContext) throws JSONException { + private void stopHandover(CallbackContext callbackContext) { stopNdefBeam(); handoverCallback = null; callbackContext.success(); @@ -399,193 +488,187 @@ private void createPendingIntent() { } private void addTechList(String[] list) { - this.addTechFilter(); - this.addToTechList(list); + this.addTechFilter(); + this.addToTechList(list); } private void removeTechList(String[] list) { - this.removeTechFilter(); - this.removeFromTechList(list); + this.removeTechFilter(); + this.removeFromTechList(list); } private void addTechFilter() { - intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)); + intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)); } - private boolean removeTechFilter() { - boolean removed = false; - Iterator iter = intentFilters.iterator(); - while (iter.hasNext()) { - IntentFilter intentFilter = iter.next(); - if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentFilter.getAction(0))) { - iter.remove(); - removed = true; + private void removeTechFilter() { + Iterator iterator = intentFilters.iterator(); + while (iterator.hasNext()) { + IntentFilter intentFilter = iterator.next(); + if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentFilter.getAction(0))) { + iterator.remove(); + } } - } - return removed; } private void addTagFilter() { - intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)); - } - - private boolean removeTagFilter() { - boolean removed = false; - Iterator iter = intentFilters.iterator(); - while (iter.hasNext()) { - IntentFilter intentFilter = iter.next(); - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentFilter.getAction(0))) { - iter.remove(); - removed = true; + intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)); + } + + private void removeTagFilter() { + Iterator iterator = intentFilters.iterator(); + while (iterator.hasNext()) { + IntentFilter intentFilter = iterator.next(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentFilter.getAction(0))) { + iterator.remove(); + } } - } - return removed; - } + } + + private void restartNfc() { + stopNfc(); + startNfc(); + } private void startNfc() { createPendingIntent(); // onResume can call startNfc before execute - getActivity().runOnUiThread(new Runnable() { - public void run() { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + getActivity().runOnUiThread(() -> { + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - if (nfcAdapter != null && !getActivity().isFinishing()) { - try { - nfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), getIntentFilters(), getTechLists()); - - if (p2pMessage != null) { - nfcAdapter.setNdefPushMessage(p2pMessage, getActivity()); - } - } catch (IllegalStateException e) { - // issue 110 - user exits app with home button while nfc is initializing - Log.w(TAG, "Illegal State Exception starting NFC. Assuming application is terminating."); + if (nfcAdapter != null && !getActivity().isFinishing()) { + try { + IntentFilter[] intentFilters = getIntentFilters(); + String[][] techLists = getTechLists(); + // don't start NFC unless some intent filters or tech lists have been added, + // because empty lists act as wildcards and receives ALL scan events + if (intentFilters.length > 0 || techLists.length > 0) { + nfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), intentFilters, techLists); } + if (p2pMessage != null) { + nfcAdapter.setNdefPushMessage(p2pMessage, getActivity()); + } + } catch (IllegalStateException e) { + // issue 110 - user exits app with home button while nfc is initializing + Log.w(TAG, "Illegal State Exception starting NFC. Assuming application is terminating."); } + } }); } private void stopNfc() { Log.d(TAG, "stopNfc"); - getActivity().runOnUiThread(new Runnable() { - public void run() { + getActivity().runOnUiThread(() -> { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - if (nfcAdapter != null) { - try { - nfcAdapter.disableForegroundDispatch(getActivity()); - } catch (IllegalStateException e) { - // issue 125 - user exits app with back button while nfc - Log.w(TAG, "Illegal State Exception stopping NFC. Assuming application is terminating."); - } + if (nfcAdapter != null) { + try { + nfcAdapter.disableForegroundDispatch(getActivity()); + } catch (IllegalStateException e) { + // issue 125 - user exits app with back button while nfc + Log.w(TAG, "Illegal State Exception stopping NFC. Assuming application is terminating."); } } }); } private void startNdefBeam(final CallbackContext callbackContext, final Uri[] uris) { - getActivity().runOnUiThread(new Runnable() { - public void run() { + getActivity().runOnUiThread(() -> { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - if (nfcAdapter == null) { - callbackContext.error(STATUS_NO_NFC); - } else if (!nfcAdapter.isNdefPushEnabled()) { - callbackContext.error(STATUS_NDEF_PUSH_DISABLED); - } else { - nfcAdapter.setOnNdefPushCompleteCallback(NfcPlugin.this, getActivity()); - try { - nfcAdapter.setBeamPushUris(uris, getActivity()); + if (nfcAdapter == null) { + callbackContext.error(STATUS_NO_NFC); + } else if (!nfcAdapter.isNdefPushEnabled()) { + callbackContext.error(STATUS_NDEF_PUSH_DISABLED); + } else { + nfcAdapter.setOnNdefPushCompleteCallback(NfcPlugin.this, getActivity()); + try { + nfcAdapter.setBeamPushUris(uris, getActivity()); - PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); - result.setKeepCallback(true); - handoverCallback = callbackContext; - callbackContext.sendPluginResult(result); + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.setKeepCallback(true); + handoverCallback = callbackContext; + callbackContext.sendPluginResult(result); - } catch (IllegalArgumentException e) { - callbackContext.error(e.getMessage()); - } + } catch (IllegalArgumentException e) { + callbackContext.error(e.getMessage()); } } }); } private void startNdefPush(final CallbackContext callbackContext) { - getActivity().runOnUiThread(new Runnable() { - public void run() { - - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - - if (nfcAdapter == null) { - callbackContext.error(STATUS_NO_NFC); - } else if (!nfcAdapter.isNdefPushEnabled()) { - callbackContext.error(STATUS_NDEF_PUSH_DISABLED); - } else { - nfcAdapter.setNdefPushMessage(p2pMessage, getActivity()); - nfcAdapter.setOnNdefPushCompleteCallback(NfcPlugin.this, getActivity()); - - PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); - result.setKeepCallback(true); - shareTagCallback = callbackContext; - callbackContext.sendPluginResult(result); - } + getActivity().runOnUiThread(() -> { + + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + + if (nfcAdapter == null) { + callbackContext.error(STATUS_NO_NFC); + } else if (!nfcAdapter.isNdefPushEnabled()) { + callbackContext.error(STATUS_NDEF_PUSH_DISABLED); + } else { + nfcAdapter.setNdefPushMessage(p2pMessage, getActivity()); + nfcAdapter.setOnNdefPushCompleteCallback(NfcPlugin.this, getActivity()); + + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.setKeepCallback(true); + shareTagCallback = callbackContext; + callbackContext.sendPluginResult(result); } }); } private void stopNdefPush() { - getActivity().runOnUiThread(new Runnable() { - public void run() { + getActivity().runOnUiThread(() -> { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - - if (nfcAdapter != null) { - nfcAdapter.setNdefPushMessage(null, getActivity()); - } + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (nfcAdapter != null) { + nfcAdapter.setNdefPushMessage(null, getActivity()); } + }); } private void stopNdefBeam() { - getActivity().runOnUiThread(new Runnable() { - public void run() { + getActivity().runOnUiThread(() -> { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - - if (nfcAdapter != null) { - nfcAdapter.setBeamPushUris(null, getActivity()); - } + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (nfcAdapter != null) { + nfcAdapter.setBeamPushUris(null, getActivity()); } + }); } private void addToTechList(String[] techs) { - techLists.add(techs); - } + techLists.add(techs); + } private void removeFromTechList(String[] techs) { - techLists.remove(techs); - } - - private boolean removeIntentFilter(String mimeType) throws MalformedMimeTypeException { - boolean removed = false; - Iterator iter = intentFilters.iterator(); - while (iter.hasNext()) { - IntentFilter intentFilter = iter.next(); - if(intentFilter.countDataTypes() > 0 ) { + Iterator iterator = techLists.iterator(); + while (iterator.hasNext()) { + String[] list = iterator.next(); + if (Arrays.equals(list, techs)) { + iterator.remove(); + } + } + } + + private void removeIntentFilter(String mimeType) { + Iterator iterator = intentFilters.iterator(); + while (iterator.hasNext()) { + IntentFilter intentFilter = iterator.next(); String mt = intentFilter.getDataType(0); if (mimeType.equals(mt)) { - iter.remove(); - removed = true; + iterator.remove(); } } - } - return removed; } private IntentFilter createIntentFilter(String mimeType) throws MalformedMimeTypeException { @@ -607,84 +690,74 @@ private String[][] getTechLists() { return techLists.toArray(new String[0][0]); } - void parseMessage() { - cordova.getThreadPool().execute(new Runnable() { - @Override - public void run() { - Log.d(TAG, "parseMessage " + getIntent()); - Intent intent = getIntent(); - String action = intent.getAction(); - Log.d(TAG, "action " + action); - if (action == null) { - return; - } + private void parseMessage() { + cordova.getThreadPool().execute(() -> { + Log.d(TAG, "parseMessage " + getIntent()); + Intent intent = getIntent(); + String action = intent.getAction(); + Log.d(TAG, "action " + action); + if (action == null) { + return; + } - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES)); - - if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) { - Ndef ndef = Ndef.get(tag); - boolean sendNdefMimeEvent = false; - if(messages.length == 1){ - NdefMessage message = (NdefMessage) messages[0]; - for(NdefRecord record : message.getRecords()) { - sendNdefMimeEvent = record.getTnf() == NdefRecord.TNF_MIME_MEDIA; - break; - } - } - if(sendNdefMimeEvent) { - fireNdefEvent(NDEF_MIME, ndef, messages); - } - - fireNdefEvent(NDEF, ndef, messages); - - } else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) { - for (String tagTech : tag.getTechList()) { - Log.d(TAG, tagTech); - if (tagTech.equals(NdefFormatable.class.getName())) { - fireNdefFormatableEvent(tag); - } else if (tagTech.equals(Ndef.class.getName())) { // - Ndef ndef = Ndef.get(tag); - fireNdefEvent(NDEF, ndef, messages); - } - } - } + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES)); - if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) { - fireTagEvent(tag); + if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) { + Ndef ndef = Ndef.get(tag); + fireNdefEvent(NDEF_MIME, ndef, messages); + + } else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) { + for (String tagTech : tag.getTechList()) { + Log.d(TAG, tagTech); + if (tagTech.equals(NdefFormatable.class.getName())) { + fireNdefFormatableEvent(tag); + } else if (tagTech.equals(Ndef.class.getName())) { // + Ndef ndef = Ndef.get(tag); + fireNdefEvent(NDEF, ndef, messages); + } } + } - setIntent(new Intent()); + if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) { + fireTagEvent(tag); } + + setIntent(new Intent()); }); } - private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) { + // Send the event data through a channel so the JavaScript side can fire the event + private void sendEvent(String type, JSONObject tag) { - JSONObject jsonObject = buildNdefJSON(ndef, messages); - String tag = jsonObject.toString(); + try { + JSONObject event = new JSONObject(); + event.put("type", type); // TAG_DEFAULT, NDEF, NDEF_MIME, NDEF_FORMATABLE + event.put("tag", tag); // JSON representing the NFC tag and NDEF messages - String command = MessageFormat.format(javaScriptEventTemplate, type, tag); - Log.v(TAG, command); - this.webView.sendJavascript(command); + PluginResult result = new PluginResult(PluginResult.Status.OK, event); + result.setKeepCallback(true); + channelCallback.sendPluginResult(result); + } catch (JSONException e) { + Log.e(TAG, "Error sending NFC event through the channel", e); + } } - private void fireNdefFormatableEvent (Tag tag) { - - String command = MessageFormat.format(javaScriptEventTemplate, NDEF_FORMATABLE, Util.tagToJSON(tag)); - Log.v(TAG, command); - this.webView.sendJavascript(command); + private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) { + JSONObject json = buildNdefJSON(ndef, messages); + sendEvent(type, json); } - private void fireTagEvent (Tag tag) { + private void fireNdefFormatableEvent(Tag tag) { + sendEvent(NDEF_FORMATABLE, Util.tagToJSON(tag)); + } - String command = MessageFormat.format(javaScriptEventTemplate, TAG_DEFAULT, Util.tagToJSON(tag)); - Log.v(TAG, command); - this.webView.sendJavascript(command); + private void fireTagEvent(Tag tag) { + sendEvent(TAG_DEFAULT, Util.tagToJSON(tag)); } - JSONObject buildNdefJSON(Ndef ndef, Parcelable[] messages) { + private JSONObject buildNdefJSON(Ndef ndef, Parcelable[] messages) { JSONObject json = Util.ndefToJSON(ndef); @@ -741,13 +814,6 @@ public void onResume(boolean multitasking) { startNfc(); } - @Override - public void onReset() { - super.onReset(); - intentFilters.clear(); - techLists.clear(); - } - @Override public void onNewIntent(Intent intent) { Log.d(TAG, "onNewIntent " + intent); @@ -769,12 +835,6 @@ private void setIntent(Intent intent) { getActivity().setIntent(intent); } - String javaScriptEventTemplate = - "var e = document.createEvent(''Events'');\n" + - "e.initEvent(''{0}'');\n" + - "e.tag = {1};\n" + - "document.dispatchEvent(e);"; - @Override public void onNdefPushComplete(NfcEvent event) { @@ -790,4 +850,180 @@ public void onNdefPushComplete(NfcEvent event) { } } + + /** + * Enable I/O operations to the tag from this TagTechnology object. + * * + * + * @param tech TagTechnology class name e.g. 'android.nfc.tech.IsoDep' or 'android.nfc.tech.NfcV' + * @param timeout tag timeout + * @param callbackContext Cordova callback context + */ + private void connect(final String tech, final int timeout, final CallbackContext callbackContext) { + this.cordova.getThreadPool().execute(() -> { + try { + + Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag == null && savedIntent != null) { + tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + } + + if (tag == null) { + Log.e(TAG, "No Tag"); + callbackContext.error("No Tag"); + return; + } + + JSONObject resultObject = new JSONObject(); + + // get technologies supported by this tag + List techList = Arrays.asList(tag.getTechList()); + if (techList.contains(tech)) { + // use reflection to call the static function Tech.get(tag) + tagTechnologyClass = Class.forName(tech); + Method method = tagTechnologyClass.getMethod("get", Tag.class); + tagTechnology = (TagTechnology) method.invoke(null, tag); + + // If the tech supports it, return maxTransceiveLength and return it to the user + try { + Method maxTransceiveLengthMethod = tagTechnologyClass.getMethod("getMaxTransceiveLength"); + resultObject.put("maxTransceiveLength", maxTransceiveLengthMethod.invoke(tagTechnology)); + } catch(NoSuchMethodException e) { + // Some technologies do not support this, so just ignore. + } catch(JSONException e) { + Log.e(TAG, "Error serializing JSON", e); + } + } + + if (tagTechnology == null) { + callbackContext.error("Tag does not support " + tech); + return; + } + + tagTechnology.connect(); + setTimeout(timeout); + callbackContext.success(resultObject); + + } catch (IOException ex) { + Log.e(TAG, "Tag connection failed", ex); + callbackContext.error("Tag connection failed"); + + // Users should never get these reflection errors + } catch (ClassNotFoundException e) { + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } catch (NoSuchMethodException e) { + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } catch (IllegalAccessException e) { + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } catch (InvocationTargetException e) { + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } + }); + } + + // Call tagTech setTimeout with reflection or fail silently + private void setTimeout(int timeout) { + if (timeout < 0) { + return; + } + try { + Method setTimeout = tagTechnologyClass.getMethod("setTimeout", int.class); + setTimeout.invoke(tagTechnology, timeout); + } catch (NoSuchMethodException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } catch (InvocationTargetException e) { + // ignore + } + } + + /** + * Disable I/O operations to the tag from this TagTechnology object, and release resources. + * + * @param callbackContext Cordova callback context + */ + private void close(CallbackContext callbackContext) { + cordova.getThreadPool().execute(() -> { + try { + + if (tagTechnology != null && tagTechnology.isConnected()) { + tagTechnology.close(); + tagTechnology = null; + callbackContext.success(); + } else { + // connection already gone + callbackContext.success(); + } + + } catch (IOException ex) { + Log.e(TAG, "Error closing nfc connection", ex); + callbackContext.error("Error closing nfc connection " + ex.getLocalizedMessage()); + } + }); + } + + /** + * Send raw commands to the tag and receive the response. + * + * @param data byte[] command to be passed to the tag + * @param callbackContext Cordova callback context + */ + private void transceive(final byte[] data, final CallbackContext callbackContext) { + cordova.getThreadPool().execute(() -> { + try { + if (tagTechnology == null) { + Log.e(TAG, "No Tech"); + callbackContext.error("No Tech"); + return; + } + if (!tagTechnology.isConnected()) { + Log.e(TAG, "Not connected"); + callbackContext.error("Not connected"); + return; + } + + // Use reflection so we can support many tag types + Method transceiveMethod = tagTechnologyClass.getMethod("transceive", byte[].class); + @SuppressWarnings("PrimitiveArrayArgumentToVarargsMethod") + byte[] response = (byte[]) transceiveMethod.invoke(tagTechnology, data); + + callbackContext.success(response); + + } catch (NoSuchMethodException e) { + String error = "TagTechnology " + tagTechnologyClass.getName() + " does not have a transceive function"; + Log.e(TAG, error, e); + callbackContext.error(error); + } catch (NullPointerException e) { + // This can happen if the tag has been closed while we're still working with it from the thread pool. + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } catch (IllegalAccessException e) { + Log.e(TAG, e.getMessage(), e); + callbackContext.error(e.getMessage()); + } catch (InvocationTargetException e) { + Log.e(TAG, e.getMessage(), e); + Throwable cause = e.getCause(); + callbackContext.error(cause.getMessage()); + } + }); + } + } +© 2021 GitHub, Inc. +Terms +Privacy +Security +Status +Docs +Contact GitHub +Pricing +API +Training +Blog +About +