Skip to content

Instantly share code, notes, and snippets.

@gercobrandwijk
Created December 7, 2021 21:41
Show Gist options
  • Save gercobrandwijk/e1ef9b2cb56a1cba17d5d0aa20876819 to your computer and use it in GitHub Desktop.
Save gercobrandwijk/e1ef9b2cb56a1cba17d5d0aa20876819 to your computer and use it in GitHub Desktop.

After npm install these two files must be copied into node_modules\phonegap-nfc\src\android\src\com\chariotsolutions\nfc\plugin\ to overwrite the already existing files. This can be done automatically by executing the copy-to-node-modules.ps1

const fs = require('fs');
fs.copyFile(
'plugin-overwrites/phonegap-nfc/NfcPlugin.java',
'node_modules/phonegap-nfc/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java',
function (err) {
if (err) throw err;
console.log('NfcPlugin.java was copied');
}
);
fs.copyFile('plugin-overwrites/phonegap-nfc/Util.java', 'node_modules/phonegap-nfc/src/android/src/com/chariotsolutions/nfc/plugin/Util.java', function (err) {
if (err) throw err;
console.log('Util.java was copied');
});
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.Date;
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;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcEvent;
import android.nfc.Tag;
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.Handler;
import android.os.Parcelable;
import android.util.Log;
public class NfcPlugin extends CordovaPlugin implements NfcAdapter.OnNdefPushCompleteCallback {
private static final String REGISTER_MIME_TYPE = "registerMimeType";
private static final String REMOVE_MIME_TYPE = "removeMimeType";
private static final String REGISTER_NDEF = "registerNdef";
private static final String REMOVE_NDEF = "removeNdef";
private static final String REGISTER_NDEF_FORMATABLE = "registerNdefFormatable";
private static final String REGISTER_DEFAULT_TAG = "registerTag";
private static final String REMOVE_DEFAULT_TAG = "removeTag";
private static final String WRITE_TAG = "writeTag";
private static final String MAKE_READ_ONLY = "makeReadOnly";
private static final String ERASE_TAG = "eraseTag";
private static final String SHARE_TAG = "shareTag";
private static final String UNSHARE_TAG = "unshareTag";
private static final String HANDOVER = "handover"; // Android Beam
private static final String STOP_HANDOVER = "stopHandover";
private static final String ENABLED = "enabled";
private static final String INIT = "init";
private static final String SHOW_SETTINGS = "showSettings";
private static final String NDEF = "ndef";
private static final String NDEF_MIME = "ndef-mime";
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<IntentFilter> intentFilters = new ArrayList<>();
private final ArrayList<String[]> 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;
private PostponedPluginResult postponedPluginResult = null;
class PostponedPluginResult {
private Date moment;
private PluginResult pluginResult;
PostponedPluginResult(Date moment, PluginResult pluginResult) {
this.moment = moment;
this.pluginResult = pluginResult;
}
boolean isValid() {
return this.moment.after(new Date(new Date().getTime() - 30000));
}
}
@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute " + action);
// showSettings can be called if NFC is disabled
// might want to skip this if NO_NFC
if (action.equalsIgnoreCase(SHOW_SETTINGS)) {
showSettings(callbackContext);
return true;
}
// the channel is set up when the plugin starts
if (action.equalsIgnoreCase(CHANNEL)) {
channelCallback = callbackContext;
if (postponedPluginResult != null) {
Log.i(TAG, "Postponed plugin result available");
if (postponedPluginResult.isValid()) {
Log.i(TAG, "Postponed plugin result is valid, resending it now");
channelCallback.sendPluginResult(postponedPluginResult.pluginResult);
} else {
Log.i(TAG, "Postponed plugin result not valid anymore, so ignoring it");
}
postponedPluginResult = null;
}
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
}
createPendingIntent();
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);
} else if (action.equalsIgnoreCase(REGISTER_NDEF)) {
registerNdef(callbackContext);
} else if (action.equalsIgnoreCase(REMOVE_NDEF)) {
removeNdef(callbackContext);
} else if (action.equalsIgnoreCase(REGISTER_NDEF_FORMATABLE)) {
registerNdefFormatable(callbackContext);
} else if (action.equals(REGISTER_DEFAULT_TAG)) {
registerDefaultTag(callbackContext);
} else if (action.equals(REMOVE_DEFAULT_TAG)) {
removeDefaultTag(callbackContext);
} else if (action.equalsIgnoreCase(WRITE_TAG)) {
writeTag(data, callbackContext);
} else if (action.equalsIgnoreCase(MAKE_READ_ONLY)) {
makeReadOnly(callbackContext);
} else if (action.equalsIgnoreCase(ERASE_TAG)) {
eraseTag(callbackContext);
} else if (action.equalsIgnoreCase(SHARE_TAG)) {
shareTag(data, callbackContext);
} else if (action.equalsIgnoreCase(UNSHARE_TAG)) {
unshareTag(callbackContext);
} else if (action.equalsIgnoreCase(HANDOVER)) {
handover(data, callbackContext);
} else if (action.equalsIgnoreCase(STOP_HANDOVER)) {
stopHandover(callbackContext);
} else if (action.equalsIgnoreCase(INIT)) {
init(callbackContext);
} else if (action.equalsIgnoreCase(ENABLED)) {
// status is checked before every call
// 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;
}
return true;
}
private String getNfcStatus() {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
if (nfcAdapter == null) {
return STATUS_NO_NFC;
} else if (!nfcAdapter.isEnabled()) {
return STATUS_NFC_DISABLED;
} else {
return STATUS_NFC_OK;
}
}
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<String> 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, null);
}
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();
restartNfc();
callbackContext.success();
}
private void removeDefaultTag(CallbackContext callbackContext) {
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()});
restartNfc();
callbackContext.success();
}
private void removeNdef(CallbackContext callbackContext) {
removeTechList(new String[]{Ndef.class.getName()});
restartNfc();
callbackContext.success();
}
private void unshareTag(CallbackContext callbackContext) {
p2pMessage = null;
stopNdefPush();
shareTagCallback = null;
callbackContext.success();
}
private void init(CallbackContext callbackContext) {
Log.d(TAG, "Enabling plugin " + getIntent());
startNfc();
if (!recycledIntent()) {
parseMessage();
}
callbackContext.success();
}
private void removeMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
String mimeType = data.getString(0);
removeIntentFilter(mimeType);
restartNfc();
callbackContext.success();
}
private void registerMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
String mimeType = "";
try {
mimeType = data.getString(0);
intentFilters.add(createIntentFilter(mimeType));
restartNfc();
callbackContext.success();
} catch (MalformedMimeTypeException e) {
callbackContext.error("Invalid MIME Type " + mimeType);
}
}
// Cheating and writing an empty record. We may actually be able to erase some tag types.
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])
};
writeNdefMessage(new NdefMessage(records), tag, callbackContext);
}
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");
}
Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
NdefRecord[] records = Util.jsonToNdefRecords(data.getString(0));
writeNdefMessage(new NdefMessage(records), tag, callbackContext);
}
private void writeNdefMessage(final NdefMessage message, final Tag tag, final CallbackContext callbackContext) {
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 {
ndef.writeNdefMessage(message);
callbackContext.success();
}
} 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());
}
});
}
private void makeReadOnly(final CallbackContext callbackContext) {
if (getIntent() == null) { // Lost Tag
callbackContext.error("Failed to make tag read only, received null intent");
return;
}
final Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag == null) {
callbackContext.error("Failed to make tag read only, tag is null");
return;
}
cordova.getThreadPool().execute(() -> {
boolean success = false;
String message = "Could not make tag read only";
Ndef ndef = Ndef.get(tag);
try {
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
message = "Tag is not writable";
} else if (ndef.canMakeReadOnly()) {
success = ndef.makeReadOnly();
} else {
message = "Tag can not be made read only";
}
} else {
message = "Tag is not NDEF";
}
} catch (IOException e) {
Log.e(TAG, "Failed to make tag read only", e);
if (e.getMessage() != null) {
message = e.getMessage();
} else {
message = e.toString();
}
}
if (success) {
callbackContext.success();
} else {
callbackContext.error(message);
}
});
}
private void shareTag(JSONArray data, CallbackContext callbackContext) throws JSONException {
NdefRecord[] records = Util.jsonToNdefRecords(data.getString(0));
this.p2pMessage = new NdefMessage(records);
startNdefPush(callbackContext);
}
// setBeamPushUris
// Every Uri you provide must have either scheme 'file' or scheme 'content'.
// Note that this takes priority over setNdefPush
//
// See http://developer.android.com/reference/android/nfc/NfcAdapter.html#setBeamPushUris(android.net.Uri[],%20android.app.Activity)
private void handover(JSONArray data, CallbackContext callbackContext) throws JSONException {
Uri[] uri = new Uri[data.length()];
for (int i = 0; i < data.length(); i++) {
uri[i] = Uri.parse(data.getString(i));
}
startNdefBeam(callbackContext, uri);
}
private void stopHandover(CallbackContext callbackContext) {
stopNdefBeam();
handoverCallback = null;
callbackContext.success();
}
private void showSettings(CallbackContext callbackContext) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
Intent intent = new Intent(android.provider.Settings.ACTION_NFC_SETTINGS);
getActivity().startActivity(intent);
} else {
Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
getActivity().startActivity(intent);
}
callbackContext.success();
}
private void createPendingIntent() {
if (pendingIntent == null) {
Activity activity = getActivity();
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
}
}
private void addTechList(String[] list) {
this.addTechFilter();
this.addToTechList(list);
}
private void removeTechList(String[] list) {
this.removeTechFilter();
this.removeFromTechList(list);
}
private void addTechFilter() {
intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED));
}
private void removeTechFilter() {
Iterator<IntentFilter> iterator = intentFilters.iterator();
while (iterator.hasNext()) {
IntentFilter intentFilter = iterator.next();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentFilter.getAction(0))) {
iterator.remove();
}
}
}
private void addTagFilter() {
intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED));
}
private void removeTagFilter() {
Iterator<IntentFilter> iterator = intentFilters.iterator();
while (iterator.hasNext()) {
IntentFilter intentFilter = iterator.next();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentFilter.getAction(0))) {
iterator.remove();
}
}
}
private void restartNfc() {
stopNfc();
startNfc();
}
private void startNfc() {
createPendingIntent(); // onResume can call startNfc before execute
getActivity().runOnUiThread(() -> {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
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(() -> {
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.");
}
}
});
}
private void startNdefBeam(final CallbackContext callbackContext, final Uri[] uris) {
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.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);
} catch (IllegalArgumentException e) {
callbackContext.error(e.getMessage());
}
}
});
}
private void startNdefPush(final CallbackContext callbackContext) {
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(() -> {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
if (nfcAdapter != null) {
nfcAdapter.setNdefPushMessage(null, getActivity());
}
});
}
private void stopNdefBeam() {
getActivity().runOnUiThread(() -> {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
if (nfcAdapter != null) {
nfcAdapter.setBeamPushUris(null, getActivity());
}
});
}
private void addToTechList(String[] techs) {
techLists.add(techs);
}
private void removeFromTechList(String[] techs) {
Iterator<String[]> iterator = techLists.iterator();
while (iterator.hasNext()) {
String[] list = iterator.next();
if (Arrays.equals(list, techs)) {
iterator.remove();
}
}
}
private void removeIntentFilter(String mimeType) {
Iterator<IntentFilter> iterator = intentFilters.iterator();
while (iterator.hasNext()) {
IntentFilter intentFilter = iterator.next();
String mt = intentFilter.getDataType(0);
if (mimeType.equals(mt)) {
iterator.remove();
}
}
}
private IntentFilter createIntentFilter(String mimeType) throws MalformedMimeTypeException {
IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
intentFilter.addDataType(mimeType);
return intentFilter;
}
private PendingIntent getPendingIntent() {
return pendingIntent;
}
private IntentFilter[] getIntentFilters() {
return intentFilters.toArray(new IntentFilter[intentFilters.size()]);
}
private String[][] getTechLists() {
//noinspection ToArrayCallWithZeroLengthArrayArgument
return techLists.toArray(new String[0][0]);
}
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);
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);
}
}
}
if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
fireTagEvent(tag, messages);
}
setIntent(new Intent());
});
}
// Send the event data through a channel so the JavaScript side can fire the event
private void sendEvent(String type, JSONObject tag) {
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
PluginResult result = new PluginResult(PluginResult.Status.OK, event);
result.setKeepCallback(true);
if (channelCallback != null) {
channelCallback.sendPluginResult(result);
} else {
postponedPluginResult = new PostponedPluginResult(new Date(), result);
}
} catch (JSONException e) {
Log.e(TAG, "Error sending NFC event through the channel", e);
}
}
private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) {
JSONObject json = buildNdefJSON(ndef, messages);
sendEvent(type, json);
}
private void fireNdefFormatableEvent(Tag tag) {
sendEvent(NDEF_FORMATABLE, Util.tagToJSON(tag, null));
}
private void fireTagEvent(Tag tag, Parcelable[] messages) {
sendEvent(TAG_DEFAULT, Util.tagToJSON(tag, messages));
}
private JSONObject buildNdefJSON(Ndef ndef, Parcelable[] messages) {
JSONObject json = Util.ndefToJSON(ndef);
// ndef is null for peer-to-peer
// ndef and messages are null for ndef format-able
if (ndef == null && messages != null) {
try {
if (messages.length > 0) {
NdefMessage message = (NdefMessage) messages[0];
json.put("ndefMessage", Util.messageToJSON(message));
// guessing type, would prefer a more definitive way to determine type
json.put("type", "NDEF Push Protocol");
}
if (messages.length > 1) {
Log.wtf(TAG, "Expected one ndefMessage but found " + messages.length);
}
} catch (JSONException e) {
// shouldn't happen
Log.e(Util.TAG, "Failed to convert ndefMessage into json", e);
}
}
return json;
}
private boolean recycledIntent() { // TODO this is a kludge, find real solution
int flags = getIntent().getFlags();
if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) {
Log.i(TAG, "Launched from history, killing recycled intent");
setIntent(new Intent());
return true;
}
return false;
}
@Override
public void onPause(boolean multitasking) {
Log.d(TAG, "onPause " + getIntent());
super.onPause(multitasking);
if (multitasking) {
// nfc can't run in background
stopNfc();
}
}
@Override
public void onResume(boolean multitasking) {
Log.d(TAG, "onResume " + getIntent());
super.onResume(multitasking);
startNfc();
}
@Override
public void onNewIntent(Intent intent) {
Log.d(TAG, "onNewIntent " + intent);
super.onNewIntent(intent);
setIntent(intent);
savedIntent = intent;
parseMessage();
}
private Activity getActivity() {
return this.cordova.getActivity();
}
private Intent getIntent() {
return getActivity().getIntent();
}
private void setIntent(Intent intent) {
getActivity().setIntent(intent);
}
@Override
public void onNdefPushComplete(NfcEvent event) {
// handover (beam) take precedence over share tag (ndef push)
if (handoverCallback != null) {
PluginResult result = new PluginResult(PluginResult.Status.OK, "Beamed Message to Peer");
result.setKeepCallback(true);
handoverCallback.sendPluginResult(result);
} else if (shareTagCallback != null) {
PluginResult result = new PluginResult(PluginResult.Status.OK, "Shared Message with Peer");
result.setKeepCallback(true);
shareTagCallback.sendPluginResult(result);
}
}
/**
* 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<String> 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());
}
});
}
}
package com.chariotsolutions.nfc.plugin;
import android.os.Parcelable;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Util {
static final String TAG = "NfcPlugin";
static JSONObject ndefToJSON(Ndef ndef) {
JSONObject json = new JSONObject();
if (ndef != null) {
try {
Tag tag = ndef.getTag();
// tag is going to be null for NDEF_FORMATABLE until NfcUtil.parseMessage is refactored
if (tag != null) {
json.put("id", byteArrayToJSON(tag.getId()));
json.put("techTypes", new JSONArray(Arrays.asList(tag.getTechList())));
}
json.put("type", translateType(ndef.getType()));
json.put("maxSize", ndef.getMaxSize());
json.put("isWritable", ndef.isWritable());
json.put("ndefMessage", messageToJSON(ndef.getCachedNdefMessage()));
// Workaround for bug in ICS (Android 4.0 and 4.0.1) where
// mTag.getTagService(); of the Ndef object sometimes returns null
// see http://issues.mroland.at/index.php?do=details&task_id=47
try {
json.put("canMakeReadOnly", ndef.canMakeReadOnly());
} catch (NullPointerException e) {
json.put("canMakeReadOnly", JSONObject.NULL);
}
} catch (JSONException e) {
Log.e(TAG, "Failed to convert ndef into json: " + ndef.toString(), e);
}
}
return json;
}
static JSONObject tagToJSON(Tag tag, Parcelable[] messages) {
JSONObject json = new JSONObject();
if (tag != null) {
try {
json.put("id", byteArrayToJSON(tag.getId()));
json.put("techTypes", new JSONArray(Arrays.asList(tag.getTechList())));
if (messages != null && messages.length > 0) {
json.put("ndefMessage", messageToJSON((NdefMessage)messages[0]));
}
} catch (JSONException e) {
Log.e(TAG, "Failed to convert tag into json: " + tag.toString(), e);
}
}
return json;
}
static String translateType(String type) {
String translation;
if (type.equals(Ndef.NFC_FORUM_TYPE_1)) {
translation = "NFC Forum Type 1";
} else if (type.equals(Ndef.NFC_FORUM_TYPE_2)) {
translation = "NFC Forum Type 2";
} else if (type.equals(Ndef.NFC_FORUM_TYPE_3)) {
translation = "NFC Forum Type 3";
} else if (type.equals(Ndef.NFC_FORUM_TYPE_4)) {
translation = "NFC Forum Type 4";
} else {
translation = type;
}
return translation;
}
static NdefRecord[] jsonToNdefRecords(String ndefMessageAsJSON) throws JSONException {
JSONArray jsonRecords = new JSONArray(ndefMessageAsJSON);
NdefRecord[] records = new NdefRecord[jsonRecords.length()];
for (int i = 0; i < jsonRecords.length(); i++) {
JSONObject record = jsonRecords.getJSONObject(i);
byte tnf = (byte) record.getInt("tnf");
byte[] type = jsonToByteArray(record.getJSONArray("type"));
byte[] id = jsonToByteArray(record.getJSONArray("id"));
byte[] payload = jsonToByteArray(record.getJSONArray("payload"));
records[i] = new NdefRecord(tnf, type, id, payload);
}
return records;
}
static JSONArray byteArrayToJSON(byte[] bytes) {
JSONArray json = new JSONArray();
for (byte aByte : bytes) {
json.put(aByte);
}
return json;
}
static byte[] jsonToByteArray(JSONArray json) throws JSONException {
byte[] b = new byte[json.length()];
for (int i = 0; i < json.length(); i++) {
b[i] = (byte) json.getInt(i);
}
return b;
}
static JSONArray messageToJSON(NdefMessage message) {
if (message == null) {
return null;
}
List<JSONObject> list = new ArrayList<JSONObject>();
for (NdefRecord ndefRecord : message.getRecords()) {
list.add(recordToJSON(ndefRecord));
}
return new JSONArray(list);
}
static JSONObject recordToJSON(NdefRecord record) {
JSONObject json = new JSONObject();
try {
json.put("tnf", record.getTnf());
json.put("type", byteArrayToJSON(record.getType()));
json.put("id", byteArrayToJSON(record.getId()));
json.put("payload", byteArrayToJSON(record.getPayload()));
} catch (JSONException e) {
//Not sure why this would happen, documentation is unclear.
Log.e(TAG, "Failed to convert ndef record into json: " + record.toString(), e);
}
return json;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment