package com.chariotsolutions.nfc.plugin; import net.rim.device.api.i18n.MessageFormat; import net.rim.device.api.io.nfc.NFCException; import net.rim.device.api.io.nfc.emulation.VirtualNDEFTag; import net.rim.device.api.io.nfc.ndef.NDEFMessage; import net.rim.device.api.io.nfc.ndef.NDEFMessageListener; import net.rim.device.api.io.nfc.ndef.NDEFRecord; import net.rim.device.api.io.nfc.ndef.NDEFTagConnection; import net.rim.device.api.io.nfc.readerwriter.*; import net.rim.device.api.system.ControlledAccessException; import org.apache.cordova.api.Plugin; import org.apache.cordova.api.PluginResult; import org.apache.cordova.api.PluginResult.Status; import org.apache.cordova.json4j.JSONObject; import org.apache.cordova.json4j.JSONArray; import org.apache.cordova.json4j.JSONException; import org.apache.cordova.util.Logger; import javax.microedition.io.Connector; import java.io.IOException; import java.util.Hashtable; // http://docs.blackberry.com/en/developers/deliverables/34480/Near_Field_Communication_1631111_11.jsp // Bug with ControlledAccessException addDetectionListener // http://supportforums.blackberry.com/t5/Java-Development/Listener-belongs-to-another-application-module/m-p/1333559#M176142 // TODO allow error detection listeners // TODO should add new JS API to specify just the types we want // nfc.addDetectionListener(ndefListener, new int[]{...}); // Target.NDEF_TAG, Target.ISO_14443_4 and Target.ISO_14443_3. // TODO allow TAG LISTENER TO WRITE public class NfcPlugin extends Plugin { private static final String TAG = "NfcPlugin: "; // supported actions private static final String REGISTER_MIME_TYPE = "registerMimeType"; private static final String REGISTER_NDEF = "registerNdef"; // private static final String REGISTER_NDEF_FORMATABLE = "registerNdefFormatable"; private static final String REGISTER_DEFAULT_TAG = "registerTag"; private static final String WRITE_TAG = "writeTag"; 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 INIT = "init"; private static final String REMOVE_MIME_TYPE = "removeMimeType"; private static final String REMOVE_NDEF = "removeNdef"; private static final String REMOVE_DEFAULT_TAG = "removeTag"; // event types 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 TagWritingListener ndefListener; private DetectionListener tagListener; private VirtualNDEFTag virtualTag; private int WAIT_FOR_WRITE_MILLIS = 3000; /** * Executes the request and returns PluginResult. * * @param action * The action to execute. * @param args * JSONArray of arguments for the plugin. * @param callbackId * The callback id used when calling back into JavaScript. * @return A PluginResult object with a status and message. */ public PluginResult execute(String action, JSONArray args, String callbackId) { Logger.debug(action + " " + args.toString()); PluginResult result; try { if(INIT.equals(action)) { Logger.debug(TAG + " Enabling plugin"); if (args.length() > 0) { WAIT_FOR_WRITE_MILLIS = args.getInt(0); } return new PluginResult(Status.OK); } else if (REGISTER_MIME_TYPE.equals(action)) { result = registerMimeListener(args); } else if (REMOVE_MIME_TYPE.equals(action)) { result = removeMimeListener(args); } else if (REGISTER_NDEF.equals(action)) { result = registerNdefListener(); } else if (REMOVE_NDEF.equals(action)) { result = removeNdefListener(); } else if (REGISTER_DEFAULT_TAG.equals(action)) { result = registerTagListener(); } else if (REMOVE_DEFAULT_TAG.equals(action)) { result = removeTagListener(); } else if (WRITE_TAG.equals(action)) { result = writeTag(args); } else if (ERASE_TAG.equals(action)) { result = eraseTag(); } else if (SHARE_TAG.equals(action)) { result = shareTag(args); } else if (UNSHARE_TAG.equals(action)) { result = unshareTag(); } else { result = new PluginResult(Status.INVALID_ACTION, TAG + "Invalid action: " + action); } } catch(NFCException e) { Logger.err(e.toString(), e); return new PluginResult(Status.ERROR, e.toString() + " action=" + action); } catch(JSONException e) { Logger.err(e.toString(), e); return new PluginResult(Status.ERROR, e.toString() + " action=" + action); } catch(ControlledAccessException e) { // User didn't allow NFC Logger.err(e.toString(), e); return new PluginResult(Status.ERROR, e.toString() + " action=" + action); } catch(SecurityException e) { // IT policy doesn't allow NFC Logger.err(e.toString(), e); return new PluginResult(Status.ERROR, e.toString() + " action=" + action); } return result; } private PluginResult registerMimeListener(JSONArray args) throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); String mimeType = args.getString(0); NDEFMessageListener listener = new NDEFMessageListener() { public void onNDEFMessageDetected(final NDEFMessage msg) { NfcPlugin.this.fireNdefEvent(NDEF_MIME, msg, null); } }; nfc.addNDEFMessageListener(listener, NDEFRecord.TNF_MEDIA, mimeType, true); return new PluginResult(Status.OK); } private PluginResult removeMimeListener(JSONArray args) throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); String mimeType = args.getString(0); nfc.removeNDEFMessageListener(NDEFRecord.TNF_MEDIA, mimeType); return new PluginResult(Status.OK); } private PluginResult registerNdefListener() throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); ndefListener = new TagWritingListener(); nfc.addDetectionListener(ndefListener, new int[]{Target.NDEF_TAG}); return new PluginResult(Status.OK); } private PluginResult removeNdefListener() throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); nfc.removeDetectionListener(ndefListener); return new PluginResult(Status.OK); } private PluginResult registerTagListener() throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); tagListener = new DetectionListener() { public void onTargetDetected(Target target) { Hashtable props = Util.getTagProperties(target); NDEFMessage message = null; try { NDEFTagConnection tagConnection = (NDEFTagConnection) Connector.open(target.getUri(Target.NDEF_TAG)); message = tagConnection.read(); // might want to handle NFCException different } catch (IOException e) { Logger.error("Failed reading tag " + e.toString()); } fireNdefEvent(TAG_DEFAULT, message, props); } }; nfc.addDetectionListener(tagListener); return new PluginResult(Status.OK); } private PluginResult removeTagListener() throws NFCException, JSONException { ReaderWriterManager nfc = ReaderWriterManager.getInstance(); nfc.removeDetectionListener(tagListener); return new PluginResult(Status.OK); } private PluginResult writeTag(JSONArray args) throws NFCException, JSONException { NDEFMessage message = Util.jsonToNdefMessage(args.getString(0)); try { if (ndefListener != null) { ndefListener.write(message); } else { return new PluginResult(Status.IO_EXCEPTION, "Tag Write Failed (Lost Tag)"); } } catch (TagLockedException e) { Logger.debug("Tag is locked"); return new PluginResult(Status.ERROR, e.getMessage()); } catch (NotEnoughSpaceException e) { Logger.debug("Tag capacity exceeded"); return new PluginResult(Status.ERROR, e.getMessage()); } catch (NFCException e) { Logger.debug("Error writing tag"); return new PluginResult(Status.ERROR, e.getMessage()); } catch (IOException e) { Logger.debug("Error connecting to tag"); return new PluginResult(Status.ERROR, e.getMessage()); } return new PluginResult(Status.OK); } private PluginResult eraseTag() throws NFCException { try { if (ndefListener != null) { ndefListener.erase(); } else { return new PluginResult(Status.IO_EXCEPTION, "Erase Failed (Lost Tag)"); } } catch (TagLockedException e) { Logger.debug("Tag is locked"); return new PluginResult(Status.ERROR, e.getMessage()); } catch (NFCException e) { Logger.debug("Error writing tag"); return new PluginResult(Status.ERROR, e.getMessage()); } return new PluginResult(Status.OK); } private PluginResult shareTag(JSONArray args) throws NFCException, JSONException { NDEFMessage message = Util.jsonToNdefMessage(args.getString(0)); virtualTag = new VirtualNDEFTag(message); virtualTag.startEmulation(); return new PluginResult(Status.OK); } private PluginResult unshareTag() throws NFCException { virtualTag.stopEmulation(); return new PluginResult(Status.OK); } //private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) { private void fireNdefEvent(String type, NDEFMessage message, Hashtable props) { String javascriptTemplate = "var e = document.createEvent(''Events'');\n" + "e.initEvent(''{0}'');\n" + "e.tag = {1};\n" + "document.dispatchEvent(e);"; JSONObject jsonObject = Util.ndefToJSON(message, props); String tag = jsonObject.toString(); Object[] args = { type, tag }; String command = MessageFormat.format(javascriptTemplate, args); Logger.debug(command); this.invokeScript(command); } // The Android code calls nfc.write() after receiving an nfcEvent // Blackberry wants tags to be written inside the DetectionListener event handler // http://www.blackberry.com/developers/docs/7.0.0api/net/rim/device/api/io/nfc/readerwriter/DetectionListener.html // // WARNING Huge hack: sleep the listener thread so the Plugin can call Javascript and then Javascript can call // nfc.write before onTargetDetected completes and target is invalid. // // Javascript has no idea when reads fail with errors. Need to look into registering error listeners. class TagWritingListener implements DetectionListener { NDEFTagConnection tagConnection; Target target; Thread t; public void onTargetDetected(Target target) { this.target = target; try { tagConnection = (NDEFTagConnection) Connector.open(target.getUri(Target.NDEF_TAG)); NDEFMessage message = tagConnection.read(); fireNdefEvent(NDEF, message, Util.getTagProperties(target)); t = Thread.currentThread(); try { Thread.sleep(WAIT_FOR_WRITE_MILLIS); } catch (InterruptedException e) { Logger.debug("Detection Listener sleep interrupted."); } } catch (NFCException e) { Logger.error("Failed to read NDEF tag" + e.toString()); } catch (IOException e) { Logger.error("Failed to connect to NDEF tag" + e.toString()); } } public void write(NDEFMessage message) throws IOException { tagConnection.write(message); t.interrupt(); } public void erase() throws NFCException { tagConnection.erase(); t.interrupt(); } } }