@iotize/device-com-nfc.cordova 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +30 -30
  2. package/README.md +673 -673
  3. package/bundles/iotize-device-com-nfc.cordova.umd.js +1108 -967
  4. package/bundles/iotize-device-com-nfc.cordova.umd.js.map +1 -1
  5. package/bundles/iotize-device-com-nfc.cordova.umd.min.js +1 -1
  6. package/bundles/iotize-device-com-nfc.cordova.umd.min.js.map +1 -1
  7. package/esm2015/iotize-device-com-nfc.cordova.js +4 -4
  8. package/esm2015/iotize-device-com-nfc.cordova.ngsummary.json +1 -1
  9. package/esm2015/public_api.js +1 -1
  10. package/esm2015/public_api.ngsummary.json +1 -1
  11. package/esm2015/www/cordova-interface.js +1 -1
  12. package/esm2015/www/cordova-interface.js.map +1 -1
  13. package/esm2015/www/errors.js +30 -30
  14. package/esm2015/www/errors.js.map +1 -1
  15. package/esm2015/www/index.js +6 -6
  16. package/esm2015/www/index.js.map +1 -1
  17. package/esm2015/www/index.metadata.json +1 -1
  18. package/esm2015/www/index.ngsummary.json +1 -1
  19. package/esm2015/www/logger.js +2 -2
  20. package/esm2015/www/logger.js.map +1 -1
  21. package/esm2015/www/ndef/definitions.js +16 -16
  22. package/esm2015/www/ndef/definitions.js.map +1 -1
  23. package/esm2015/www/ndef/parse-ndef-message.js +293 -164
  24. package/esm2015/www/ndef/parse-ndef-message.js.map +1 -1
  25. package/esm2015/www/ndef/parse-ndef-message.metadata.json +1 -1
  26. package/esm2015/www/ndef/parse-ndef-message.ngsummary.json +1 -1
  27. package/esm2015/www/nfc-com-protocol.js +169 -169
  28. package/esm2015/www/nfc-com-protocol.js.map +1 -1
  29. package/esm2015/www/tap-ndef/definitions.js +1 -1
  30. package/esm2015/www/tap-ndef/definitions.js.map +1 -1
  31. package/esm2015/www/tap-ndef/index.js +2 -2
  32. package/esm2015/www/tap-ndef/index.js.map +1 -1
  33. package/esm2015/www/tap-ndef/parse-ndef-message.js +51 -51
  34. package/esm2015/www/tap-ndef/parse-ndef-message.js.map +1 -1
  35. package/esm2015/www/utility.js +10 -10
  36. package/esm2015/www/utility.js.map +1 -1
  37. package/esm2015/www/utility.ngsummary.json +1 -1
  38. package/fesm2015/iotize-device-com-nfc.cordova.js +555 -427
  39. package/fesm2015/iotize-device-com-nfc.cordova.js.map +1 -1
  40. package/iotize-device-com-nfc.cordova.d.ts +4 -4
  41. package/iotize-device-com-nfc.cordova.metadata.json +1 -1
  42. package/package.json +2 -2
  43. package/plugin.xml +98 -98
  44. package/public_api.d.ts +1 -1
  45. package/src/android/.gradle/4.10.1/fileChanges/last-build.bin +0 -0
  46. package/src/android/.gradle/4.10.1/fileHashes/fileHashes.bin +0 -0
  47. package/src/android/.gradle/4.10.1/fileHashes/fileHashes.lock +0 -0
  48. package/src/android/.gradle/4.10.1/gc.properties +0 -0
  49. package/src/android/build.gradle +38 -38
  50. package/src/android/gradle/wrapper/gradle-wrapper.properties +6 -6
  51. package/src/android/gradlew +172 -172
  52. package/src/android/gradlew.bat +84 -84
  53. package/src/android/local.properties +8 -8
  54. package/src/android/src/com/chariotsolutions/nfc/plugin/JSONBuilder.java +60 -60
  55. package/src/android/src/com/chariotsolutions/nfc/plugin/NfcPlugin.java +1151 -1151
  56. package/src/android/src/com/chariotsolutions/nfc/plugin/NfcPluginError.java +15 -15
  57. package/src/android/src/com/chariotsolutions/nfc/plugin/PluginResponse.java +85 -85
  58. package/src/android/src/com/chariotsolutions/nfc/plugin/Util.java +140 -140
  59. package/src/ios/AppDelegate+NFC.swift +51 -51
  60. package/src/ios/NFCHelpers.swift +144 -144
  61. package/src/ios/NFCNDEFDelegate.swift +78 -78
  62. package/src/ios/NFCTagReader.swift +506 -506
  63. package/www/cordova-interface.d.ts +34 -34
  64. package/www/errors.d.ts +17 -17
  65. package/www/index.d.ts +6 -6
  66. package/www/logger.d.ts +2 -2
  67. package/www/ndef/definitions.d.ts +15 -15
  68. package/www/ndef/parse-ndef-message.d.ts +12 -69
  69. package/www/nfc-com-protocol.d.ts +36 -36
  70. package/www/phonegap-nfc.js +911 -911
  71. package/www/tap-ndef/definitions.d.ts +25 -25
  72. package/www/tap-ndef/index.d.ts +2 -2
  73. package/www/tap-ndef/parse-ndef-message.d.ts +6 -6
  74. package/www/utility.d.ts +2 -2
@@ -1,1151 +1,1151 @@
1
- package com.chariotsolutions.nfc.plugin;
2
-
3
- import android.app.Activity;
4
- import android.app.PendingIntent;
5
- import android.content.Context;
6
- import android.content.Intent;
7
- import android.content.IntentFilter;
8
- import android.content.IntentFilter.MalformedMimeTypeException;
9
- import android.nfc.FormatException;
10
- import android.nfc.NdefMessage;
11
- import android.nfc.NdefRecord;
12
- import android.nfc.NfcAdapter;
13
- import android.nfc.Tag;
14
- import android.nfc.TagLostException;
15
- import android.nfc.tech.Ndef;
16
- import android.nfc.tech.NdefFormatable;
17
- import android.nfc.tech.TagTechnology;
18
- import android.os.Bundle;
19
- import android.os.Parcelable;
20
- import android.util.Log;
21
- import android.widget.Toast;
22
-
23
- import com.iotize.android.communication.client.impl.EncryptionAlgo;
24
- import com.iotize.android.communication.client.impl.TapClient;
25
- import com.iotize.android.communication.client.impl.protocol.ProtocolFactory;
26
- import com.iotize.android.communication.protocol.nfc.NFCIntentParser;
27
- import com.iotize.android.communication.protocol.nfc.NFCProtocol;
28
- import com.iotize.android.communication.protocol.nfc.NFCProtocolFactory;
29
- import com.iotize.android.core.util.Helper;
30
- import com.iotize.android.device.device.impl.IoTizeDevice;
31
-
32
- import org.apache.cordova.CallbackContext;
33
- import org.apache.cordova.CordovaArgs;
34
- import org.apache.cordova.CordovaPlugin;
35
- import org.apache.cordova.PluginResult;
36
- import org.json.JSONArray;
37
- import org.json.JSONException;
38
- import org.json.JSONObject;
39
-
40
- import java.io.IOException;
41
- import java.lang.reflect.Field;
42
- import java.lang.reflect.InvocationTargetException;
43
- import java.lang.reflect.Method;
44
- import java.util.ArrayList;
45
- import java.util.Arrays;
46
- import java.util.Iterator;
47
- import java.util.List;
48
-
49
- import io.reactivex.annotations.NonNull;
50
- import io.reactivex.annotations.Nullable;
51
-
52
- // using wildcard imports so we can support Cordova 3.x
53
-
54
- public class NfcPlugin extends CordovaPlugin {
55
- private static final String REGISTER_MIME_TYPE = "registerMimeType";
56
- private static final String REMOVE_MIME_TYPE = "removeMimeType";
57
- private static final String REGISTER_NDEF = "registerNdef";
58
- private static final String REMOVE_NDEF = "removeNdef";
59
- private static final String REGISTER_NDEF_FORMATABLE = "registerNdefFormatable";
60
- private static final String REGISTER_DEFAULT_TAG = "registerTag";
61
- private static final String REMOVE_DEFAULT_TAG = "removeTag";
62
- private static final String WRITE_TAG = "writeTag";
63
- private static final String ERASE_TAG = "eraseTag";
64
- private static final String ENABLED = "enabled";
65
- private static final String INIT = "init";
66
- private static final String SHOW_SETTINGS = "showSettings";
67
-
68
- private static final String NDEF = "ndef";
69
- private static final String NDEF_MIME = "ndef-mime";
70
- private static final String NDEF_FORMATABLE = "ndef-formatable";
71
- private static final String TAG_DEFAULT = "tag";
72
-
73
- private static final String READER_MODE = "readerMode";
74
-
75
- // TagTechnology IsoDep, NfcA, NfcB, NfcV, NfcF, MifareClassic, MifareUltralight
76
- private static final String CONNECT_TAP = "connect";
77
- private static final String CONNECT_RAW = "connectRaw";
78
- private static final String CLOSE = "close";
79
- private static final String TRANSCEIVE_TAP = "transceiveTap";
80
- private static final String TRANSCEIVE = "transceive";
81
-
82
- private static final String NFC_TAP_DEVICE = "nfc-tap-device";
83
- private static final String PREF_ENABLE_TAP_DEVICE_DISCOVERY = "EnableNFCTapDeviceDiscovery";
84
- private static final String PREF_TAP_DEVICE_MIME_TYPE = "NFCTapDeviceMimeType";
85
- private static final String PREF_ENABLE_NFC_PAIRING = "EnableNFCPairing";
86
- private static final String PREF_ENABLE_ENCRYPTION_WITH_NFC = "EnableEncryptionWithNFC";
87
- private static final String PREF_NFC_PAIRING_DONE_TOAST_MESSAGE = "NFCParingDoneToastMessage";
88
- private static final String REGISTER_NFC_TAP_DEVICE = "registerTapDevice";
89
- private static final String SET_TAP_DEVICE_DISCOVERY_ENABLED = "setTapDeviceDiscoveryEnabled";
90
- private static final String ANDROID_NFC_TECH_CLASS_PASS = "android.nfc.tech.";
91
- private static final String DEFAULT_NFC_TECH_CLASS_PASS = ANDROID_NFC_TECH_CLASS_PASS + "NfcV";
92
-
93
- @Nullable
94
- private TagTechnology tagTechnology = null;
95
-
96
- @NonNull
97
- private String _lastTechName = DEFAULT_NFC_TECH_CLASS_PASS;
98
-
99
- @Nullable
100
- private Class<?> tagTechnologyClass;
101
-
102
- private static final String CHANNEL = "channel";
103
-
104
- private static final String STATUS_NFC_OK = "NFC_OK";
105
- private static final String STATUS_NO_NFC = "NO_NFC";
106
- private static final String STATUS_NFC_DISABLED = "NFC_DISABLED";
107
- private static final String STATUS_NDEF_PUSH_DISABLED = "NDEF_PUSH_DISABLED";
108
-
109
- private static final String TAG = "NfcPlugin";
110
- private final List<IntentFilter> intentFilters = new ArrayList<>();
111
- private final ArrayList<String[]> techLists = new ArrayList<>();
112
-
113
- private NdefMessage p2pMessage = null;
114
- private PendingIntent pendingIntent = null;
115
-
116
- private Intent savedIntent = null;
117
- private long savedIntentTime = 0;
118
-
119
- private CallbackContext readerModeCallback;
120
- @Nullable
121
- private CallbackContext channelCallback;
122
-
123
- @Nullable
124
- private NFCProtocol nfcProtocol;
125
- @Nullable
126
- private IoTizeDevice mLastTapDiscovered;
127
- @Nullable
128
- private Intent mLastTapDiscoveredIntent;
129
-
130
- private boolean _isTapDeviceDiscoveryEnabled = true;
131
-
132
- @Override
133
- public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
134
-
135
- Log.d(TAG, "execute " + action);
136
-
137
- // showSettings can be called if NFC is disabled
138
- // might want to skip this if NO_NFC
139
- if (action.equalsIgnoreCase(SHOW_SETTINGS)) {
140
- showSettings(callbackContext);
141
- return true;
142
- }
143
-
144
- // the channel is set up when the plugin starts
145
- if (action.equalsIgnoreCase(CHANNEL)) {
146
- channelCallback = callbackContext;
147
- return true; // short circuit
148
- }
149
-
150
- if (!getNfcStatus().equals(STATUS_NFC_OK)) {
151
- callbackContext.error(getNfcStatus());
152
- return true; // short circuit
153
- }
154
-
155
- createPendingIntent();
156
-
157
- if (action.equalsIgnoreCase(READER_MODE)) {
158
- int flags = data.getInt(0);
159
- readerMode(flags, callbackContext);
160
-
161
- } else if (action.equalsIgnoreCase(REGISTER_MIME_TYPE)) {
162
- registerMimeType(data, callbackContext);
163
- } else if (action.equalsIgnoreCase(REGISTER_NFC_TAP_DEVICE)) {
164
- // JSONObject jsonStringOptions = data.getJSONObject(0);
165
- registerTapDevice(callbackContext);
166
- } else if (action.equalsIgnoreCase(REMOVE_MIME_TYPE)) {
167
- removeMimeType(data, callbackContext);
168
-
169
- } else if (action.equalsIgnoreCase(REGISTER_NDEF)) {
170
- registerNdef(callbackContext);
171
- } else if (action.equalsIgnoreCase(REMOVE_NDEF)) {
172
- removeNdef(callbackContext);
173
-
174
- } else if (action.equalsIgnoreCase(REGISTER_NDEF_FORMATABLE)) {
175
- registerNdefFormatable(callbackContext);
176
-
177
- } else if (action.equals(REGISTER_DEFAULT_TAG)) {
178
- registerDefaultTag(callbackContext);
179
-
180
- } else if (action.equals(REMOVE_DEFAULT_TAG)) {
181
- removeDefaultTag(callbackContext);
182
-
183
- } else if (action.equalsIgnoreCase(WRITE_TAG)) {
184
- writeTag(data, callbackContext);
185
-
186
- } else if (action.equalsIgnoreCase(ERASE_TAG)) {
187
- eraseTag(callbackContext);
188
-
189
- } else if (action.equalsIgnoreCase(INIT)) {
190
- init(callbackContext);
191
-
192
- } else if (action.equalsIgnoreCase(ENABLED)) {
193
- // status is checked before every call
194
- // if code made it here, NFC is enabled
195
- callbackContext.success(STATUS_NFC_OK);
196
-
197
- } else if (action.equalsIgnoreCase(CONNECT_TAP)) {
198
- String tech = data.getString(0);
199
- int timeout = data.optInt(1, -1);
200
- connectTap(tech, timeout, callbackContext);
201
-
202
- } else if (action.equalsIgnoreCase(CONNECT_RAW)) {
203
- String tech = data.getString(0);
204
- int timeout = data.optInt(1, -1);
205
- connectRaw(tech, timeout, callbackContext);
206
-
207
- } else if (action.equalsIgnoreCase(TRANSCEIVE)) {
208
- CordovaArgs args = new CordovaArgs(data); // execute is using the old signature with JSON data
209
-
210
- byte[] command = args.getArrayBuffer(0);
211
- transceiveRaw(command, callbackContext);
212
-
213
- } else if (action.equalsIgnoreCase(TRANSCEIVE_TAP)) {
214
- CordovaArgs args = new CordovaArgs(data); // execute is using the old signature with JSON data
215
-
216
- byte[] command = args.getArrayBuffer(0);
217
- transceiveTap(command, callbackContext);
218
-
219
- } else if (action.equalsIgnoreCase(CLOSE)) {
220
- close(callbackContext);
221
-
222
- } else if (action.equalsIgnoreCase(SET_TAP_DEVICE_DISCOVERY_ENABLED)) {
223
- CordovaArgs args = new CordovaArgs(data);
224
- this._isTapDeviceDiscoveryEnabled = args.getBoolean(0);
225
- } else {
226
- // invalid action
227
- callbackContext.error("Invalid NFC action \"" + action +"\"");
228
- return false;
229
- }
230
-
231
- return true;
232
- }
233
-
234
- private String getNfcStatus() {
235
- NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
236
- if (nfcAdapter == null) {
237
- return STATUS_NO_NFC;
238
- } else if (!nfcAdapter.isEnabled()) {
239
- return STATUS_NFC_DISABLED;
240
- } else {
241
- return STATUS_NFC_OK;
242
- }
243
- }
244
-
245
- private void readerMode(int flags, CallbackContext callbackContext) {
246
- Bundle extras = new Bundle(); // not used
247
- readerModeCallback = callbackContext;
248
- getActivity().runOnUiThread(() -> {
249
- NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
250
- nfcAdapter.enableReaderMode(getActivity(), callback, flags, extras);
251
- });
252
-
253
- }
254
-
255
- private NfcAdapter.ReaderCallback callback = new NfcAdapter.ReaderCallback() {
256
- @Override
257
- public void onTagDiscovered(Tag tag) {
258
-
259
- JSONObject json;
260
-
261
- // If the tag supports Ndef, try and return an Ndef message
262
- List<String> techList = Arrays.asList(tag.getTechList());
263
- if (techList.contains(Ndef.class.getName())) {
264
- Ndef ndef = Ndef.get(tag);
265
- json = Util.ndefToJSON(ndef);
266
- } else {
267
- json = Util.tagToJSON(tag);
268
- }
269
-
270
- PluginResult result = new PluginResult(PluginResult.Status.OK, json);
271
- result.setKeepCallback(true);
272
- readerModeCallback.sendPluginResult(result);
273
-
274
- }
275
- };
276
-
277
- @NonNull
278
- private NFCIntentParser getIntentParser(Intent intent) {
279
- NFCIntentParser parser = new NFCIntentParser(intent);
280
- Tag tag = parser.getTag();
281
- if (tag == null) {
282
- Log.wtf(TAG, "Intent has a nfc tag null. Intent = " + intent);
283
- throw new IllegalArgumentException("NFC tag is null. Retry nfc tap ?");
284
- }
285
- return parser;
286
- }
287
-
288
- private IoTizeDevice createTapFromIntent(Intent intent) throws Exception {
289
- Context context = getActivity();
290
- NFCIntentParser parser = this.getIntentParser(intent);
291
- ProtocolFactory nfcProtocolFactory = new NFCProtocolFactory(parser.getTag());
292
- IoTizeDevice tap = IoTizeDevice.fromProtocol(nfcProtocolFactory.create(context));
293
-
294
- boolean nfcPairingEnabled = preferences.getBoolean(PREF_ENABLE_NFC_PAIRING, true);
295
- boolean encryptionEnabled = preferences.getBoolean(PREF_ENABLE_ENCRYPTION_WITH_NFC, false);
296
- tap.connect();
297
- if (nfcPairingEnabled) {
298
- byte[] response = tap.nfcPairing();
299
- }
300
- if (encryptionEnabled) {
301
- tap.encryption(true, true);
302
- }
303
- return tap;
304
- }
305
-
306
- /**
307
- * @param intent
308
- * @return true if nfc intent has been handld
309
- */
310
- private boolean onTapDeviceDiscoveredIntent(Intent intent) {
311
- try {
312
- Log.d(TAG, "creating tap device...");
313
- IoTizeDevice tap = this.createTapFromIntent(intent);
314
- mLastTapDiscovered = tap;
315
- mLastTapDiscoveredIntent = intent;
316
- String nfcPairingDoneUserFeedback = preferences.getString(PREF_NFC_PAIRING_DONE_TOAST_MESSAGE, "NFC pairing done!");
317
- if (nfcPairingDoneUserFeedback.length() > 0) {
318
- Activity activity = cordova.getActivity();
319
- if (activity != null) {
320
- activity.runOnUiThread(() -> {
321
- try {
322
- Toast.makeText(activity, nfcPairingDoneUserFeedback, Toast.LENGTH_LONG).show();
323
- } catch (Throwable err) {
324
- Log.w(TAG, err.getMessage(), err);
325
- }
326
- });
327
- }
328
- }
329
- fireTapDeviceEvent(tap, intent);
330
- return true;
331
- } catch (Exception e) {
332
- Log.w(TAG, e.getMessage(), e);
333
- return false;
334
- }
335
-
336
- }
337
-
338
- private void registerDefaultTag(CallbackContext callbackContext) {
339
- addTagFilter();
340
- restartNfc();
341
- callbackContext.success();
342
- if (savedIntent != null) {
343
- long tagOld = System.currentTimeMillis()-savedIntentTime;
344
- if (tagOld < 3000) {
345
- if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(savedIntent.getAction())) {
346
- Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
347
- if (tag != null ) {
348
- Log.i(TAG, "registerDefaultTag() fire recently tapped tag (" + tagOld + "ms ago)");
349
- Parcelable[] messages = savedIntent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
350
- fireTagEvent(tag, messages);
351
- savedIntentTime = 0;
352
- }
353
- }
354
- }
355
- }
356
- }
357
-
358
- private void removeDefaultTag(CallbackContext callbackContext) {
359
- removeTagFilter();
360
- restartNfc();
361
- callbackContext.success();
362
- }
363
-
364
- private void registerNdefFormatable(CallbackContext callbackContext) {
365
- addTechList(new String[]{NdefFormatable.class.getName()});
366
- restartNfc();
367
- callbackContext.success();
368
- }
369
-
370
- private void registerNdef(CallbackContext callbackContext) {
371
- addTechList(new String[]{Ndef.class.getName()});
372
- restartNfc();
373
- callbackContext.success();
374
- }
375
-
376
- private void removeNdef(CallbackContext callbackContext) {
377
- removeTechList(new String[]{Ndef.class.getName()});
378
- restartNfc();
379
- callbackContext.success();
380
- }
381
-
382
- private void init(CallbackContext callbackContext) {
383
- Log.d(TAG, "Enabling plugin " + getIntent());
384
-
385
- startNfc();
386
- if (!recycledIntent()) {
387
- parseMessage();
388
- }
389
- callbackContext.success();
390
- }
391
-
392
- private void removeMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
393
- String mimeType = data.getString(0);
394
- removeIntentFilter(mimeType);
395
- restartNfc();
396
- callbackContext.success();
397
- }
398
-
399
- private void registerMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
400
- String mimeType = "";
401
- try {
402
- mimeType = data.getString(0);
403
- intentFilters.add(createIntentFilter(mimeType));
404
- restartNfc();
405
- callbackContext.success();
406
- } catch (MalformedMimeTypeException e) {
407
- callbackContext.error("Invalid MIME Type " + mimeType);
408
- }
409
- }
410
-
411
- private void registerTapDevice(CallbackContext callbackContext) throws JSONException {
412
- Log.d(TAG, "registerTapDevice");
413
- if (mLastTapDiscovered != null) {
414
- Log.d(TAG, "a tap was detected before function call registerTapDevice");
415
- fireTapDeviceEvent(mLastTapDiscovered, mLastTapDiscoveredIntent);
416
- }
417
- callbackContext.success();
418
- }
419
-
420
- private void initializeTapDeviceListener() {
421
- if (this.isTapDeviceDiscoveryEnabled()) {
422
- addTechList(new String[]{Ndef.class.getName()});
423
- String mimeType = getTapDeviceMimeType();
424
- try {
425
- if (mimeType != null) {
426
- intentFilters.add(createIntentFilter(mimeType));
427
- }
428
- } catch (MalformedMimeTypeException e) {
429
- Log.e(TAG, "MalformedMimeTypeException " + e.getMessage(), e);
430
- }
431
-
432
- }
433
-
434
- }
435
-
436
- private String getTapDeviceMimeType() {
437
- return preferences.getString(PREF_TAP_DEVICE_MIME_TYPE, null);
438
- }
439
-
440
- private boolean isTapDeviceDiscoveryEnabled() {
441
- return this._isTapDeviceDiscoveryEnabled && preferences.getBoolean(PREF_ENABLE_TAP_DEVICE_DISCOVERY, false);
442
- }
443
-
444
- // Cheating and writing an empty record. We may actually be able to erase some tag types.
445
- private void eraseTag(CallbackContext callbackContext) {
446
- Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
447
- NdefRecord[] records = {
448
- new NdefRecord(NdefRecord.TNF_EMPTY, new byte[0], new byte[0], new byte[0])
449
- };
450
- writeNdefMessage(new NdefMessage(records), tag, callbackContext);
451
- }
452
-
453
- private void writeTag(JSONArray data, CallbackContext callbackContext) throws JSONException {
454
- if (getIntent() == null) { // TODO remove this and handle LostTag
455
- callbackContext.error("Failed to write tag, received null intent");
456
- }
457
-
458
- Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
459
- NdefRecord[] records = Util.jsonToNdefRecords(data.getString(0));
460
- writeNdefMessage(new NdefMessage(records), tag, callbackContext);
461
- }
462
-
463
- private void writeNdefMessage(final NdefMessage message, final Tag tag,
464
- final CallbackContext callbackContext) {
465
- cordova.getThreadPool().execute(() -> {
466
- try {
467
- Ndef ndef = Ndef.get(tag);
468
- if (ndef != null) {
469
- ndef.connect();
470
-
471
- if (ndef.isWritable()) {
472
- int size = message.toByteArray().length;
473
- if (ndef.getMaxSize() < size) {
474
- callbackContext.error("Tag capacity is " + ndef.getMaxSize() +
475
- " bytes, message is " + size + " bytes.");
476
- } else {
477
- ndef.writeNdefMessage(message);
478
- callbackContext.success();
479
- }
480
- } else {
481
- callbackContext.error("Tag is read only");
482
- }
483
- ndef.close();
484
- } else {
485
- NdefFormatable formatable = NdefFormatable.get(tag);
486
- if (formatable != null) {
487
- formatable.connect();
488
- formatable.format(message);
489
- callbackContext.success();
490
- formatable.close();
491
- } else {
492
- callbackContext.error("Tag doesn't support NDEF");
493
- }
494
- }
495
- } catch (FormatException e) {
496
- callbackContext.error(e.getMessage());
497
- } catch (TagLostException e) {
498
- callbackContext.error(e.getMessage());
499
- } catch (IOException e) {
500
- callbackContext.error(e.getMessage());
501
- }
502
- });
503
- }
504
-
505
- private void showSettings(CallbackContext callbackContext) {
506
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
507
- Intent intent = new Intent(android.provider.Settings.ACTION_NFC_SETTINGS);
508
- getActivity().startActivity(intent);
509
- } else {
510
- Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
511
- getActivity().startActivity(intent);
512
- }
513
- callbackContext.success();
514
- }
515
-
516
- private void createPendingIntent() {
517
- if (pendingIntent == null) {
518
- Activity activity = getActivity();
519
- Intent intent = new Intent(activity, activity.getClass());
520
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
521
- int pendingIntentFlags = 0;
522
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
523
- pendingIntentFlags = PendingIntent.FLAG_MUTABLE;
524
- }
525
- pendingIntent = PendingIntent.getActivity(activity, 0, intent, pendingIntentFlags);
526
- }
527
- }
528
-
529
- private void addTechList(String[] list) {
530
- this.addTechFilter();
531
- this.addToTechList(list);
532
- }
533
-
534
- private void removeTechList(String[] list) {
535
- this.removeTechFilter();
536
- this.removeFromTechList(list);
537
- }
538
-
539
- private void addTechFilter() {
540
- intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED));
541
- }
542
-
543
- private void removeTechFilter() {
544
- Iterator<IntentFilter> iterator = intentFilters.iterator();
545
- while (iterator.hasNext()) {
546
- IntentFilter intentFilter = iterator.next();
547
- if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentFilter.getAction(0))) {
548
- iterator.remove();
549
- }
550
- }
551
- }
552
-
553
- private void addTagFilter() {
554
- intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED));
555
- }
556
-
557
- private void removeTagFilter() {
558
- Iterator<IntentFilter> iterator = intentFilters.iterator();
559
- while (iterator.hasNext()) {
560
- IntentFilter intentFilter = iterator.next();
561
- if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentFilter.getAction(0))) {
562
- iterator.remove();
563
- }
564
- }
565
- }
566
-
567
- private void restartNfc() {
568
- stopNfc();
569
- startNfc();
570
- }
571
-
572
- private void startNfc() {
573
- createPendingIntent(); // onResume can call startNfc before execute
574
-
575
- getActivity().runOnUiThread(() -> {
576
- NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
577
-
578
- if (nfcAdapter != null && !getActivity().isFinishing()) {
579
- try {
580
- IntentFilter[] intentFilters = getIntentFilters();
581
- String[][] techLists = getTechLists();
582
- // don't start NFC unless some intent filters or tech lists have been added,
583
- // because empty lists act as wildcards and receives ALL scan events
584
- if (intentFilters.length > 0 || techLists.length > 0) {
585
- nfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), intentFilters, techLists);
586
- }
587
-
588
- } catch (IllegalStateException e) {
589
- // issue 110 - user exits app with home button while nfc is initializing
590
- Log.w(TAG, "Illegal State Exception starting NFC. Assuming application is terminating.");
591
- }
592
-
593
- }
594
- });
595
- }
596
-
597
- private void stopNfc() {
598
- Log.d(TAG, "stopNfc");
599
- getActivity().runOnUiThread(() -> {
600
-
601
- NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
602
-
603
- if (nfcAdapter != null) {
604
- try {
605
- nfcAdapter.disableForegroundDispatch(getActivity());
606
- } catch (IllegalStateException e) {
607
- // issue 125 - user exits app with back button while nfc
608
- Log.w(TAG, "Illegal State Exception stopping NFC. Assuming application is terminating.");
609
- }
610
- }
611
- });
612
- }
613
-
614
- private void addToTechList(String[] techs) {
615
- techLists.add(techs);
616
- }
617
-
618
- private void removeFromTechList(String[] techs) {
619
- Iterator<String[]> iterator = techLists.iterator();
620
- while (iterator.hasNext()) {
621
- String[] list = iterator.next();
622
- if (Arrays.equals(list, techs)) {
623
- iterator.remove();
624
- }
625
- }
626
- }
627
-
628
- private void removeIntentFilter(String mimeType) {
629
- Iterator<IntentFilter> iterator = intentFilters.iterator();
630
- while (iterator.hasNext()) {
631
- IntentFilter intentFilter = iterator.next();
632
- String mt = intentFilter.getDataType(0);
633
- if (mimeType.equals(mt)) {
634
- iterator.remove();
635
- }
636
- }
637
- }
638
-
639
- private IntentFilter createIntentFilter(String mimeType) throws MalformedMimeTypeException {
640
- IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
641
- intentFilter.addDataType(mimeType);
642
- return intentFilter;
643
- }
644
-
645
- private PendingIntent getPendingIntent() {
646
- return pendingIntent;
647
- }
648
-
649
- private IntentFilter[] getIntentFilters() {
650
- return intentFilters.toArray(new IntentFilter[intentFilters.size()]);
651
- }
652
-
653
- private String[][] getTechLists() {
654
- //noinspection ToArrayCallWithZeroLengthArrayArgument
655
- return techLists.toArray(new String[0][0]);
656
- }
657
-
658
- private void parseMessage() {
659
- cordova.getThreadPool().execute(() -> {
660
- try {
661
- Log.d(TAG, "parseMessage " + getIntent());
662
- Intent intent = getIntent();
663
- String action = intent.getAction();
664
- Log.d(TAG, "action " + action);
665
- if (action == null) {
666
- return;
667
- }
668
-
669
- final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
670
- if (tag != null) {
671
- Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
672
-
673
-
674
- if (isTapDeviceDiscoveryEnabled() && isIoTizeTag(tag)) {
675
- onTapDeviceDiscoveredIntent(intent);
676
- }
677
-
678
- if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
679
- Ndef ndef = Ndef.get(tag);
680
- fireNdefEvent(NDEF_MIME, ndef, messages);
681
- savedIntent = intent;
682
- savedIntentTime = System.currentTimeMillis();
683
- fireTagEvent(tag, messages);
684
-
685
- } else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
686
- this._fireTagEventsFromTechList(tag, messages);
687
- fireTagEvent(tag, messages);
688
- } else if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
689
- fireTagEvent(tag, messages);
690
- }
691
- }
692
- }
693
- catch (RuntimeException err) {
694
- Log.w(TAG, "Unhandled error with parseMessage()", err);
695
- }
696
-
697
- setIntent(new Intent());
698
- });
699
- }
700
-
701
- private void _fireTagEventsFromTechList(@NonNull Tag tag, @NonNull Parcelable[] messages) {
702
- for (String tagTech : tag.getTechList()) {
703
- Log.d(TAG, tagTech);
704
- if (tagTech.equals(NdefFormatable.class.getName())) {
705
- fireNdefFormatableEvent(tag);
706
- } else if (tagTech.equals(Ndef.class.getName())) { //
707
- this._fireNdefEvent(tag, messages);
708
- }
709
- }
710
- }
711
-
712
- private void _fireNdefEvent(@NonNull Tag tag, @NonNull Parcelable[] messages) {
713
- Ndef ndef = Ndef.get(tag);
714
- fireNdefEvent(NDEF, ndef, messages);
715
- }
716
-
717
- private boolean isIoTizeTag(@Nullable Tag tag) {
718
- if (tag == null) {
719
- return false;
720
- }
721
- boolean hasNfcV = Arrays.stream(tag.getTechList()).anyMatch(s -> s.endsWith("NfcV"));
722
- if (!hasNfcV) {
723
- return false;
724
- }
725
- Ndef ndef = Ndef.get(tag);
726
- if (ndef == null) {
727
- return false;
728
- }
729
- NdefMessage ndefMessages = ndef.getCachedNdefMessage();
730
- if (ndefMessages == null) {
731
- return false;
732
- }
733
- NdefRecord[] records = ndefMessages.getRecords();
734
-
735
- return records.length >= 4; // TODO improve condition
736
- }
737
-
738
- private void sendEvent(String type, JSONObject tag, JSONObject tap) {
739
- try {
740
- JSONObject event = new JSONObject();
741
- event.put("type", type); // TAG_DEFAULT, NDEF, NDEF_MIME, NDEF_FORMATABLE
742
- event.put("tag", tag); // JSON representing the NFC tag and NDEF messages
743
- event.put("tap", tap);
744
- sendEvent(event);
745
- } catch (JSONException e) {
746
- Log.e(TAG, "Error sending NFC event through the channel", e);
747
- }
748
-
749
- }
750
-
751
- private void sendEvent(String type, JSONObject tag) {
752
- try {
753
- JSONObject event = new JSONObject();
754
- event.put("type", type); // TAG_DEFAULT, NDEF, NDEF_MIME, NDEF_FORMATABLE
755
- event.put("tag", tag); // JSON representing the NFC tag and NDEF messages
756
- sendEvent(event);
757
- } catch (JSONException e) {
758
- Log.e(TAG, "Error sending NFC event through the channel", e);
759
- }
760
- }
761
-
762
- // Send the event data through a channel so the JavaScript side can fire the event
763
- private void sendEvent(JSONObject event) {
764
- PluginResult result = new PluginResult(PluginResult.Status.OK, event);
765
- result.setKeepCallback(true);
766
- if (channelCallback != null) {
767
- channelCallback.sendPluginResult(result);
768
- }
769
- }
770
-
771
- private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) {
772
- try {
773
- JSONObject json = buildNdefJSON(ndef, messages);
774
- sendEvent(type, json);
775
- } catch (Throwable e) {
776
- Log.w(TAG, "Failed to fire NDef event", e);
777
- }
778
- }
779
-
780
- private void fireTapDeviceEvent(IoTizeDevice tap, Intent intent) {
781
- try {
782
- Log.d(TAG, "fireTapDeviceEvent " + tap);
783
- Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
784
- Ndef ndef = Ndef.get(tag);
785
- Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
786
-
787
- sendEvent(NFC_TAP_DEVICE, buildNdefJSON(ndef, messages), buildTapJSON(tap));
788
- } catch (JSONException e) {
789
- Log.e(TAG, e.getMessage(), e);
790
- } catch (Throwable e) {
791
- Log.w(TAG, "Failed to fire Tap Device event", e);
792
- }
793
- }
794
-
795
- private JSONObject buildTapJSON(IoTizeDevice tap) throws JSONException {
796
- JSONObject tapInfo = new JSONObject();
797
- tapInfo.put("nfcPairingDone", true); // TODO
798
- JSONObject encryptionJSON = new JSONObject();
799
- boolean encryptionEnabled = false;
800
- if (tap.isEncryptionEnabled()) {
801
- EncryptionAlgo encryptionAlgo = tap.getClient().getEncryptionAlgo();
802
- if (encryptionAlgo != null) {
803
- encryptionEnabled = true;
804
- encryptionJSON.put("enabled", true);
805
- JSONObject keysOptions = new JSONObject();
806
- keysOptions.put("sessionKey", Util.byteArrayToJSON(encryptionAlgo.getKey()));
807
- int frameCounter = 0;
808
- try {
809
- // TODO change with frameCounter
810
- TapClient client = tap.getClient();
811
- Field field = client.getClass().getDeclaredField("frameCounter");
812
- field.setAccessible(true);
813
- frameCounter = (int) field.get(client);
814
- } catch (NoSuchFieldException e) {
815
- Log.w(TAG, e.getMessage(), e);
816
- } catch (IllegalAccessException e) {
817
- Log.w(TAG, e.getMessage(), e);
818
- }
819
- keysOptions.put("sessionKeyHex", Helper.ByteArrayToHexString(encryptionAlgo.getKey()));
820
- // keysOptions.put("ivEncode", Util.byteArrayToJSON(());
821
- // keysOptions.put("ivDecode", Util.byteArrayToJSON(());
822
-
823
- encryptionJSON.put("keys", keysOptions);
824
- encryptionJSON.put("frameCounter", frameCounter);
825
- }
826
- }
827
- encryptionJSON.put("enabled", encryptionEnabled);
828
- tapInfo.put("encryption", encryptionJSON);
829
- return tapInfo;
830
- }
831
-
832
- private void fireNdefFormatableEvent(Tag tag) {
833
- sendEvent(NDEF_FORMATABLE, Util.tagToJSON(tag));
834
- }
835
-
836
- private void fireTagEvent(Tag tag, Parcelable[] messages) {
837
- if (Arrays.asList(tag.getTechList()).contains(Ndef.class.getName())) {
838
- sendEvent(TAG_DEFAULT, buildNdefJSON(Ndef.get(tag), messages));
839
- }
840
- else {
841
- sendEvent(TAG_DEFAULT, Util.tagToJSON(tag));
842
- }
843
- }
844
-
845
- /**
846
- * May throw a java.lang.SecurityException error if Tag is out of date (tested on Android 13)
847
- */
848
- private JSONObject buildNdefJSON(Ndef ndef, Parcelable[] messages) throws SecurityException {
849
-
850
- JSONObject json = Util.ndefToJSON(ndef);
851
-
852
- // ndef is null for peer-to-peer
853
- // ndef and messages are null for ndef format-able
854
- if (ndef == null && messages != null) {
855
-
856
- try {
857
-
858
- if (messages.length > 0) {
859
- NdefMessage message = (NdefMessage) messages[0];
860
- json.put("ndefMessage", Util.messageToJSON(message));
861
- // guessing type, would prefer a more definitive way to determine type
862
- json.put("type", "NDEF Push Protocol");
863
- }
864
-
865
- if (messages.length > 1) {
866
- Log.wtf(TAG, "Expected one ndefMessage but found " + messages.length);
867
- }
868
-
869
- } catch (JSONException e) {
870
- // shouldn't happen
871
- Log.e(Util.TAG, "Failed to convert ndefMessage into json", e);
872
- }
873
- }
874
- return json;
875
- }
876
-
877
- private boolean recycledIntent() { // TODO this is a kludge, find real solution
878
-
879
- int flags = getIntent().getFlags();
880
- if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) {
881
- Log.i(TAG, "Launched from history, killing recycled intent");
882
- setIntent(new Intent());
883
- return true;
884
- }
885
- return false;
886
- }
887
-
888
- @Override
889
- public void onStart() {
890
- super.onStart();
891
- this.initializeTapDeviceListener();
892
- }
893
-
894
- @Override
895
- public void onPause(boolean multitasking) {
896
- Log.d(TAG, "onPause " + getIntent());
897
- super.onPause(multitasking);
898
- if (multitasking) {
899
- // nfc can't run in background
900
- stopNfc();
901
- }
902
- }
903
-
904
- @Override
905
- public void onResume(boolean multitasking) {
906
- Log.d(TAG, "onResume " + getIntent());
907
- super.onResume(multitasking);
908
- startNfc();
909
- }
910
-
911
- @Override
912
- public void onNewIntent(Intent intent) {
913
- Log.d(TAG, "onNewIntent " + intent);
914
- super.onNewIntent(intent);
915
- setIntent(intent);
916
- savedIntent = intent;
917
- parseMessage();
918
- }
919
-
920
- private Activity getActivity() {
921
- return this.cordova.getActivity();
922
- }
923
-
924
- private Intent getIntent() {
925
- return getActivity().getIntent();
926
- }
927
-
928
- private void setIntent(Intent intent) {
929
- getActivity().setIntent(intent);
930
- }
931
-
932
- /**
933
- * Enable I/O operations to the tag from this TagTechnology object.
934
- * *
935
- *
936
- * @param tech TagTechnology class name e.g. 'android.nfc.tech.IsoDep' or 'android.nfc.tech.NfcV'
937
- * @param timeout tag timeout
938
- * @param callbackContext Cordova callback context
939
- */
940
- private void connectRaw(final String tech, final int timeout,
941
- final CallbackContext callbackContext) {
942
- final String fullTechName = !tech.startsWith(ANDROID_NFC_TECH_CLASS_PASS) ? ANDROID_NFC_TECH_CLASS_PASS + tech : tech;
943
- this.cordova.getThreadPool().execute(() -> {
944
- try {
945
- this._lastTechName = fullTechName;
946
- this._initIntentTag(fullTechName);
947
-
948
- if (tagTechnology == null) {
949
- callbackContext.error("Tag does not support " + tech);
950
- return;
951
- }
952
-
953
- if (!tagTechnology.isConnected()) {
954
- tagTechnology.connect();
955
- }
956
- setTimeout(timeout);
957
- Log.d(TAG, "NFC Connection successful");
958
- callbackContext.success();
959
- } catch (IOException ex) {
960
- Log.e(TAG, "Tag connection failed", ex);
961
- callbackContext.error("Tag connection failed");
962
- } catch (Throwable e) {
963
- Log.e(TAG, e.getMessage(), e);
964
- callbackContext.error(e.getMessage());
965
- }
966
- });
967
- }
968
-
969
- /**
970
- * Perform Tap NFCProtocol connect call
971
- *
972
- * @param tech TagTechnology class name e.g. 'android.nfc.tech.IsoDep' or 'android.nfc.tech.NfcV'
973
- * @param timeout tag timeout
974
- * @param callbackContext Cordova callback context
975
- */
976
- private void connectTap(final String tech, final int timeout,
977
- final CallbackContext callbackContext) {
978
- final String fullTechName = !tech.startsWith(ANDROID_NFC_TECH_CLASS_PASS) ? ANDROID_NFC_TECH_CLASS_PASS + tech : tech;
979
- this.cordova.getThreadPool().execute(() -> {
980
- try {
981
- this._lastTechName = fullTechName;
982
- this._initIntentTag(fullTechName);
983
-
984
- if (nfcProtocol == null) {
985
- callbackContext.error("Tag does not support " + tech);
986
- return;
987
- }
988
-
989
- nfcProtocol.connect();
990
- setTimeout(timeout);
991
- Log.d(TAG, "NFC Connection successful");
992
- callbackContext.success();
993
- } catch (IOException ex) {
994
- Log.e(TAG, "Tag connection failed", ex);
995
- callbackContext.error("Tag connection failed");
996
- } catch (Throwable e) {
997
- Log.e(TAG, e.getMessage(), e);
998
- callbackContext.error(e.getMessage());
999
- }
1000
- });
1001
- }
1002
-
1003
- private void _initIntentTag(final String tech) throws Exception {
1004
- Intent intent = getIntent();
1005
- Tag tag = null;
1006
- if (intent != null) {
1007
- tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1008
- }
1009
- if (tag == null && savedIntent != null) {
1010
- tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1011
- }
1012
-
1013
- if (tag == null) {
1014
- Log.e(TAG, "No Tag");
1015
- throw new Exception("No Tag");
1016
- }
1017
-
1018
- // get technologies supported by this tag
1019
- List<String> techList = Arrays.asList(tag.getTechList());
1020
- if (!techList.contains(tech)) {
1021
- throw new Exception("Tech " + tech + " not available");
1022
- }
1023
- // use reflection to call the static function Tech.get(tag)
1024
- tagTechnologyClass = Class.forName(tech);
1025
- nfcProtocol = NFCProtocol.create(tag);
1026
- Method method = tagTechnologyClass.getMethod("get", Tag.class);
1027
- tagTechnology = (TagTechnology) method.invoke(null, tag);
1028
- if (tagTechnology == null) {
1029
- Log.e(TAG, "No Tag Technology");
1030
- throw new Exception("No Tag");
1031
- }
1032
- }
1033
-
1034
- private void setTimeout(int timeout) {
1035
- if (timeout < 0) {
1036
- return;
1037
- }
1038
- if (nfcProtocol != null) {
1039
- nfcProtocol.getConfiguration().connectionTimeoutMillis = timeout;
1040
- }
1041
- }
1042
-
1043
- /**
1044
- * Disable I/O operations to the tag from this TagTechnology object, and release resources.
1045
- *
1046
- * @param callbackContext Cordova callback context
1047
- */
1048
- private void close(CallbackContext callbackContext) {
1049
- cordova.getThreadPool().execute(() -> {
1050
- try {
1051
- _lastTechName = DEFAULT_NFC_TECH_CLASS_PASS;
1052
- if (nfcProtocol != null && nfcProtocol.isConnected()) {
1053
- nfcProtocol.disconnect();
1054
- }
1055
- callbackContext.success();
1056
-
1057
- } catch (Exception ex) {
1058
- Log.e(TAG, "Error closing nfc connection", ex);
1059
- callbackContext.error("Error closing nfc connection " + ex.getLocalizedMessage());
1060
- }
1061
- finally {
1062
- nfcProtocol = null;
1063
- tagTechnology = null;
1064
- tagTechnologyClass = null;
1065
- }
1066
- });
1067
- }
1068
-
1069
- /**
1070
- * Send raw commands to the tag and receive the response.
1071
- *
1072
- * @param data byte[] command to be passed to the tag
1073
- * @param callbackContext Cordova callback context
1074
- */
1075
- private void transceiveRaw(final byte[] data, final CallbackContext callbackContext) {
1076
- cordova.getThreadPool().execute(() -> {
1077
- try {
1078
- this._connectIntentTagIfNeeded();
1079
- Method transceiveMethod = tagTechnologyClass.getMethod("transceive", byte[].class);
1080
- try {
1081
- @SuppressWarnings("PrimitiveArrayArgumentToVarargsMethod")
1082
- byte[] response = (byte[]) transceiveMethod.invoke(tagTechnology, data);
1083
- callbackContext.success(Helper.ByteArrayToHexString(response));
1084
- }
1085
- catch (InvocationTargetException e) {
1086
- Throwable targetException = e.getTargetException();
1087
- String errorMessage = targetException.getMessage();
1088
- if (errorMessage != null && (errorMessage.endsWith("is out of date") ||
1089
- errorMessage.contains("Call connect() first"))) {
1090
- this._connectIntentTag();
1091
- // Retry
1092
- byte[] response = (byte[]) transceiveMethod.invoke(tagTechnology, data);
1093
- callbackContext.success(Helper.ByteArrayToHexString(response));
1094
- }
1095
- else {
1096
- throw e;
1097
- }
1098
- }
1099
- }
1100
- catch (InvocationTargetException e) {
1101
- String msg = e.getTargetException().getMessage();
1102
- Log.e(TAG, msg, e);
1103
- callbackContext.error(msg);
1104
- }
1105
- catch (Throwable e) {
1106
- Log.e(TAG, e.getMessage(), e);
1107
- callbackContext.error(e.getMessage());
1108
- }
1109
- });
1110
- }
1111
-
1112
- private void _connectIntentTag() throws Exception {
1113
- this._initIntentTag(this._lastTechName);
1114
- tagTechnology.connect();
1115
- }
1116
-
1117
- private void _connectIntentTagIfNeeded() throws Exception {
1118
- if (tagTechnology == null) {
1119
- this._initIntentTag(this._lastTechName);
1120
- tagTechnology.connect();
1121
- }
1122
- }
1123
-
1124
- /**
1125
- * Send raw commands to the tag and receive the response.
1126
- *
1127
- * @param data byte[] command to be passed to the tag
1128
- * @param callbackContext Cordova callback context
1129
- */
1130
- private void transceiveTap(final byte[] data, final CallbackContext callbackContext) {
1131
- cordova.getThreadPool().execute(() -> {
1132
- try {
1133
- if (nfcProtocol == null) {
1134
- Log.e(TAG, "No Tech");
1135
- callbackContext.error("No Tech");
1136
- return;
1137
- }
1138
- if (!nfcProtocol.isConnected()) {
1139
- Log.e(TAG, "Not connected");
1140
- callbackContext.error("Not connected");
1141
- return;
1142
- }
1143
- byte[] response = nfcProtocol.send(data);
1144
- callbackContext.success(Helper.ByteArrayToHexString(response));
1145
- } catch (Exception e) {
1146
- Log.e(TAG, e.getMessage(), e);
1147
- callbackContext.error(e.getMessage());
1148
- }
1149
- });
1150
- }
1151
- }
1
+ package com.chariotsolutions.nfc.plugin;
2
+
3
+ import android.app.Activity;
4
+ import android.app.PendingIntent;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.content.IntentFilter;
8
+ import android.content.IntentFilter.MalformedMimeTypeException;
9
+ import android.nfc.FormatException;
10
+ import android.nfc.NdefMessage;
11
+ import android.nfc.NdefRecord;
12
+ import android.nfc.NfcAdapter;
13
+ import android.nfc.Tag;
14
+ import android.nfc.TagLostException;
15
+ import android.nfc.tech.Ndef;
16
+ import android.nfc.tech.NdefFormatable;
17
+ import android.nfc.tech.TagTechnology;
18
+ import android.os.Bundle;
19
+ import android.os.Parcelable;
20
+ import android.util.Log;
21
+ import android.widget.Toast;
22
+
23
+ import com.iotize.android.communication.client.impl.EncryptionAlgo;
24
+ import com.iotize.android.communication.client.impl.TapClient;
25
+ import com.iotize.android.communication.client.impl.protocol.ProtocolFactory;
26
+ import com.iotize.android.communication.protocol.nfc.NFCIntentParser;
27
+ import com.iotize.android.communication.protocol.nfc.NFCProtocol;
28
+ import com.iotize.android.communication.protocol.nfc.NFCProtocolFactory;
29
+ import com.iotize.android.core.util.Helper;
30
+ import com.iotize.android.device.device.impl.IoTizeDevice;
31
+
32
+ import org.apache.cordova.CallbackContext;
33
+ import org.apache.cordova.CordovaArgs;
34
+ import org.apache.cordova.CordovaPlugin;
35
+ import org.apache.cordova.PluginResult;
36
+ import org.json.JSONArray;
37
+ import org.json.JSONException;
38
+ import org.json.JSONObject;
39
+
40
+ import java.io.IOException;
41
+ import java.lang.reflect.Field;
42
+ import java.lang.reflect.InvocationTargetException;
43
+ import java.lang.reflect.Method;
44
+ import java.util.ArrayList;
45
+ import java.util.Arrays;
46
+ import java.util.Iterator;
47
+ import java.util.List;
48
+
49
+ import io.reactivex.annotations.NonNull;
50
+ import io.reactivex.annotations.Nullable;
51
+
52
+ // using wildcard imports so we can support Cordova 3.x
53
+
54
+ public class NfcPlugin extends CordovaPlugin {
55
+ private static final String REGISTER_MIME_TYPE = "registerMimeType";
56
+ private static final String REMOVE_MIME_TYPE = "removeMimeType";
57
+ private static final String REGISTER_NDEF = "registerNdef";
58
+ private static final String REMOVE_NDEF = "removeNdef";
59
+ private static final String REGISTER_NDEF_FORMATABLE = "registerNdefFormatable";
60
+ private static final String REGISTER_DEFAULT_TAG = "registerTag";
61
+ private static final String REMOVE_DEFAULT_TAG = "removeTag";
62
+ private static final String WRITE_TAG = "writeTag";
63
+ private static final String ERASE_TAG = "eraseTag";
64
+ private static final String ENABLED = "enabled";
65
+ private static final String INIT = "init";
66
+ private static final String SHOW_SETTINGS = "showSettings";
67
+
68
+ private static final String NDEF = "ndef";
69
+ private static final String NDEF_MIME = "ndef-mime";
70
+ private static final String NDEF_FORMATABLE = "ndef-formatable";
71
+ private static final String TAG_DEFAULT = "tag";
72
+
73
+ private static final String READER_MODE = "readerMode";
74
+
75
+ // TagTechnology IsoDep, NfcA, NfcB, NfcV, NfcF, MifareClassic, MifareUltralight
76
+ private static final String CONNECT_TAP = "connect";
77
+ private static final String CONNECT_RAW = "connectRaw";
78
+ private static final String CLOSE = "close";
79
+ private static final String TRANSCEIVE_TAP = "transceiveTap";
80
+ private static final String TRANSCEIVE = "transceive";
81
+
82
+ private static final String NFC_TAP_DEVICE = "nfc-tap-device";
83
+ private static final String PREF_ENABLE_TAP_DEVICE_DISCOVERY = "EnableNFCTapDeviceDiscovery";
84
+ private static final String PREF_TAP_DEVICE_MIME_TYPE = "NFCTapDeviceMimeType";
85
+ private static final String PREF_ENABLE_NFC_PAIRING = "EnableNFCPairing";
86
+ private static final String PREF_ENABLE_ENCRYPTION_WITH_NFC = "EnableEncryptionWithNFC";
87
+ private static final String PREF_NFC_PAIRING_DONE_TOAST_MESSAGE = "NFCParingDoneToastMessage";
88
+ private static final String REGISTER_NFC_TAP_DEVICE = "registerTapDevice";
89
+ private static final String SET_TAP_DEVICE_DISCOVERY_ENABLED = "setTapDeviceDiscoveryEnabled";
90
+ private static final String ANDROID_NFC_TECH_CLASS_PASS = "android.nfc.tech.";
91
+ private static final String DEFAULT_NFC_TECH_CLASS_PASS = ANDROID_NFC_TECH_CLASS_PASS + "NfcV";
92
+
93
+ @Nullable
94
+ private TagTechnology tagTechnology = null;
95
+
96
+ @NonNull
97
+ private String _lastTechName = DEFAULT_NFC_TECH_CLASS_PASS;
98
+
99
+ @Nullable
100
+ private Class<?> tagTechnologyClass;
101
+
102
+ private static final String CHANNEL = "channel";
103
+
104
+ private static final String STATUS_NFC_OK = "NFC_OK";
105
+ private static final String STATUS_NO_NFC = "NO_NFC";
106
+ private static final String STATUS_NFC_DISABLED = "NFC_DISABLED";
107
+ private static final String STATUS_NDEF_PUSH_DISABLED = "NDEF_PUSH_DISABLED";
108
+
109
+ private static final String TAG = "NfcPlugin";
110
+ private final List<IntentFilter> intentFilters = new ArrayList<>();
111
+ private final ArrayList<String[]> techLists = new ArrayList<>();
112
+
113
+ private NdefMessage p2pMessage = null;
114
+ private PendingIntent pendingIntent = null;
115
+
116
+ private Intent savedIntent = null;
117
+ private long savedIntentTime = 0;
118
+
119
+ private CallbackContext readerModeCallback;
120
+ @Nullable
121
+ private CallbackContext channelCallback;
122
+
123
+ @Nullable
124
+ private NFCProtocol nfcProtocol;
125
+ @Nullable
126
+ private IoTizeDevice mLastTapDiscovered;
127
+ @Nullable
128
+ private Intent mLastTapDiscoveredIntent;
129
+
130
+ private boolean _isTapDeviceDiscoveryEnabled = true;
131
+
132
+ @Override
133
+ public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
134
+
135
+ Log.d(TAG, "execute " + action);
136
+
137
+ // showSettings can be called if NFC is disabled
138
+ // might want to skip this if NO_NFC
139
+ if (action.equalsIgnoreCase(SHOW_SETTINGS)) {
140
+ showSettings(callbackContext);
141
+ return true;
142
+ }
143
+
144
+ // the channel is set up when the plugin starts
145
+ if (action.equalsIgnoreCase(CHANNEL)) {
146
+ channelCallback = callbackContext;
147
+ return true; // short circuit
148
+ }
149
+
150
+ if (!getNfcStatus().equals(STATUS_NFC_OK)) {
151
+ callbackContext.error(getNfcStatus());
152
+ return true; // short circuit
153
+ }
154
+
155
+ createPendingIntent();
156
+
157
+ if (action.equalsIgnoreCase(READER_MODE)) {
158
+ int flags = data.getInt(0);
159
+ readerMode(flags, callbackContext);
160
+
161
+ } else if (action.equalsIgnoreCase(REGISTER_MIME_TYPE)) {
162
+ registerMimeType(data, callbackContext);
163
+ } else if (action.equalsIgnoreCase(REGISTER_NFC_TAP_DEVICE)) {
164
+ // JSONObject jsonStringOptions = data.getJSONObject(0);
165
+ registerTapDevice(callbackContext);
166
+ } else if (action.equalsIgnoreCase(REMOVE_MIME_TYPE)) {
167
+ removeMimeType(data, callbackContext);
168
+
169
+ } else if (action.equalsIgnoreCase(REGISTER_NDEF)) {
170
+ registerNdef(callbackContext);
171
+ } else if (action.equalsIgnoreCase(REMOVE_NDEF)) {
172
+ removeNdef(callbackContext);
173
+
174
+ } else if (action.equalsIgnoreCase(REGISTER_NDEF_FORMATABLE)) {
175
+ registerNdefFormatable(callbackContext);
176
+
177
+ } else if (action.equals(REGISTER_DEFAULT_TAG)) {
178
+ registerDefaultTag(callbackContext);
179
+
180
+ } else if (action.equals(REMOVE_DEFAULT_TAG)) {
181
+ removeDefaultTag(callbackContext);
182
+
183
+ } else if (action.equalsIgnoreCase(WRITE_TAG)) {
184
+ writeTag(data, callbackContext);
185
+
186
+ } else if (action.equalsIgnoreCase(ERASE_TAG)) {
187
+ eraseTag(callbackContext);
188
+
189
+ } else if (action.equalsIgnoreCase(INIT)) {
190
+ init(callbackContext);
191
+
192
+ } else if (action.equalsIgnoreCase(ENABLED)) {
193
+ // status is checked before every call
194
+ // if code made it here, NFC is enabled
195
+ callbackContext.success(STATUS_NFC_OK);
196
+
197
+ } else if (action.equalsIgnoreCase(CONNECT_TAP)) {
198
+ String tech = data.getString(0);
199
+ int timeout = data.optInt(1, -1);
200
+ connectTap(tech, timeout, callbackContext);
201
+
202
+ } else if (action.equalsIgnoreCase(CONNECT_RAW)) {
203
+ String tech = data.getString(0);
204
+ int timeout = data.optInt(1, -1);
205
+ connectRaw(tech, timeout, callbackContext);
206
+
207
+ } else if (action.equalsIgnoreCase(TRANSCEIVE)) {
208
+ CordovaArgs args = new CordovaArgs(data); // execute is using the old signature with JSON data
209
+
210
+ byte[] command = args.getArrayBuffer(0);
211
+ transceiveRaw(command, callbackContext);
212
+
213
+ } else if (action.equalsIgnoreCase(TRANSCEIVE_TAP)) {
214
+ CordovaArgs args = new CordovaArgs(data); // execute is using the old signature with JSON data
215
+
216
+ byte[] command = args.getArrayBuffer(0);
217
+ transceiveTap(command, callbackContext);
218
+
219
+ } else if (action.equalsIgnoreCase(CLOSE)) {
220
+ close(callbackContext);
221
+
222
+ } else if (action.equalsIgnoreCase(SET_TAP_DEVICE_DISCOVERY_ENABLED)) {
223
+ CordovaArgs args = new CordovaArgs(data);
224
+ this._isTapDeviceDiscoveryEnabled = args.getBoolean(0);
225
+ } else {
226
+ // invalid action
227
+ callbackContext.error("Invalid NFC action \"" + action +"\"");
228
+ return false;
229
+ }
230
+
231
+ return true;
232
+ }
233
+
234
+ private String getNfcStatus() {
235
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
236
+ if (nfcAdapter == null) {
237
+ return STATUS_NO_NFC;
238
+ } else if (!nfcAdapter.isEnabled()) {
239
+ return STATUS_NFC_DISABLED;
240
+ } else {
241
+ return STATUS_NFC_OK;
242
+ }
243
+ }
244
+
245
+ private void readerMode(int flags, CallbackContext callbackContext) {
246
+ Bundle extras = new Bundle(); // not used
247
+ readerModeCallback = callbackContext;
248
+ getActivity().runOnUiThread(() -> {
249
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
250
+ nfcAdapter.enableReaderMode(getActivity(), callback, flags, extras);
251
+ });
252
+
253
+ }
254
+
255
+ private NfcAdapter.ReaderCallback callback = new NfcAdapter.ReaderCallback() {
256
+ @Override
257
+ public void onTagDiscovered(Tag tag) {
258
+
259
+ JSONObject json;
260
+
261
+ // If the tag supports Ndef, try and return an Ndef message
262
+ List<String> techList = Arrays.asList(tag.getTechList());
263
+ if (techList.contains(Ndef.class.getName())) {
264
+ Ndef ndef = Ndef.get(tag);
265
+ json = Util.ndefToJSON(ndef);
266
+ } else {
267
+ json = Util.tagToJSON(tag);
268
+ }
269
+
270
+ PluginResult result = new PluginResult(PluginResult.Status.OK, json);
271
+ result.setKeepCallback(true);
272
+ readerModeCallback.sendPluginResult(result);
273
+
274
+ }
275
+ };
276
+
277
+ @NonNull
278
+ private NFCIntentParser getIntentParser(Intent intent) {
279
+ NFCIntentParser parser = new NFCIntentParser(intent);
280
+ Tag tag = parser.getTag();
281
+ if (tag == null) {
282
+ Log.wtf(TAG, "Intent has a nfc tag null. Intent = " + intent);
283
+ throw new IllegalArgumentException("NFC tag is null. Retry nfc tap ?");
284
+ }
285
+ return parser;
286
+ }
287
+
288
+ private IoTizeDevice createTapFromIntent(Intent intent) throws Exception {
289
+ Context context = getActivity();
290
+ NFCIntentParser parser = this.getIntentParser(intent);
291
+ ProtocolFactory nfcProtocolFactory = new NFCProtocolFactory(parser.getTag());
292
+ IoTizeDevice tap = IoTizeDevice.fromProtocol(nfcProtocolFactory.create(context));
293
+
294
+ boolean nfcPairingEnabled = preferences.getBoolean(PREF_ENABLE_NFC_PAIRING, true);
295
+ boolean encryptionEnabled = preferences.getBoolean(PREF_ENABLE_ENCRYPTION_WITH_NFC, false);
296
+ tap.connect();
297
+ if (nfcPairingEnabled) {
298
+ byte[] response = tap.nfcPairing();
299
+ }
300
+ if (encryptionEnabled) {
301
+ tap.encryption(true, true);
302
+ }
303
+ return tap;
304
+ }
305
+
306
+ /**
307
+ * @param intent
308
+ * @return true if nfc intent has been handld
309
+ */
310
+ private boolean onTapDeviceDiscoveredIntent(Intent intent) {
311
+ try {
312
+ Log.d(TAG, "creating tap device...");
313
+ IoTizeDevice tap = this.createTapFromIntent(intent);
314
+ mLastTapDiscovered = tap;
315
+ mLastTapDiscoveredIntent = intent;
316
+ String nfcPairingDoneUserFeedback = preferences.getString(PREF_NFC_PAIRING_DONE_TOAST_MESSAGE, "NFC pairing done!");
317
+ if (nfcPairingDoneUserFeedback.length() > 0) {
318
+ Activity activity = cordova.getActivity();
319
+ if (activity != null) {
320
+ activity.runOnUiThread(() -> {
321
+ try {
322
+ Toast.makeText(activity, nfcPairingDoneUserFeedback, Toast.LENGTH_LONG).show();
323
+ } catch (Throwable err) {
324
+ Log.w(TAG, err.getMessage(), err);
325
+ }
326
+ });
327
+ }
328
+ }
329
+ fireTapDeviceEvent(tap, intent);
330
+ return true;
331
+ } catch (Exception e) {
332
+ Log.w(TAG, e.getMessage(), e);
333
+ return false;
334
+ }
335
+
336
+ }
337
+
338
+ private void registerDefaultTag(CallbackContext callbackContext) {
339
+ addTagFilter();
340
+ restartNfc();
341
+ callbackContext.success();
342
+ if (savedIntent != null) {
343
+ long tagOld = System.currentTimeMillis()-savedIntentTime;
344
+ if (tagOld < 3000) {
345
+ if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(savedIntent.getAction())) {
346
+ Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
347
+ if (tag != null ) {
348
+ Log.i(TAG, "registerDefaultTag() fire recently tapped tag (" + tagOld + "ms ago)");
349
+ Parcelable[] messages = savedIntent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
350
+ fireTagEvent(tag, messages);
351
+ savedIntentTime = 0;
352
+ }
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ private void removeDefaultTag(CallbackContext callbackContext) {
359
+ removeTagFilter();
360
+ restartNfc();
361
+ callbackContext.success();
362
+ }
363
+
364
+ private void registerNdefFormatable(CallbackContext callbackContext) {
365
+ addTechList(new String[]{NdefFormatable.class.getName()});
366
+ restartNfc();
367
+ callbackContext.success();
368
+ }
369
+
370
+ private void registerNdef(CallbackContext callbackContext) {
371
+ addTechList(new String[]{Ndef.class.getName()});
372
+ restartNfc();
373
+ callbackContext.success();
374
+ }
375
+
376
+ private void removeNdef(CallbackContext callbackContext) {
377
+ removeTechList(new String[]{Ndef.class.getName()});
378
+ restartNfc();
379
+ callbackContext.success();
380
+ }
381
+
382
+ private void init(CallbackContext callbackContext) {
383
+ Log.d(TAG, "Enabling plugin " + getIntent());
384
+
385
+ startNfc();
386
+ if (!recycledIntent()) {
387
+ parseMessage();
388
+ }
389
+ callbackContext.success();
390
+ }
391
+
392
+ private void removeMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
393
+ String mimeType = data.getString(0);
394
+ removeIntentFilter(mimeType);
395
+ restartNfc();
396
+ callbackContext.success();
397
+ }
398
+
399
+ private void registerMimeType(JSONArray data, CallbackContext callbackContext) throws JSONException {
400
+ String mimeType = "";
401
+ try {
402
+ mimeType = data.getString(0);
403
+ intentFilters.add(createIntentFilter(mimeType));
404
+ restartNfc();
405
+ callbackContext.success();
406
+ } catch (MalformedMimeTypeException e) {
407
+ callbackContext.error("Invalid MIME Type " + mimeType);
408
+ }
409
+ }
410
+
411
+ private void registerTapDevice(CallbackContext callbackContext) throws JSONException {
412
+ Log.d(TAG, "registerTapDevice");
413
+ if (mLastTapDiscovered != null) {
414
+ Log.d(TAG, "a tap was detected before function call registerTapDevice");
415
+ fireTapDeviceEvent(mLastTapDiscovered, mLastTapDiscoveredIntent);
416
+ }
417
+ callbackContext.success();
418
+ }
419
+
420
+ private void initializeTapDeviceListener() {
421
+ if (this.isTapDeviceDiscoveryEnabled()) {
422
+ addTechList(new String[]{Ndef.class.getName()});
423
+ String mimeType = getTapDeviceMimeType();
424
+ try {
425
+ if (mimeType != null) {
426
+ intentFilters.add(createIntentFilter(mimeType));
427
+ }
428
+ } catch (MalformedMimeTypeException e) {
429
+ Log.e(TAG, "MalformedMimeTypeException " + e.getMessage(), e);
430
+ }
431
+
432
+ }
433
+
434
+ }
435
+
436
+ private String getTapDeviceMimeType() {
437
+ return preferences.getString(PREF_TAP_DEVICE_MIME_TYPE, null);
438
+ }
439
+
440
+ private boolean isTapDeviceDiscoveryEnabled() {
441
+ return this._isTapDeviceDiscoveryEnabled && preferences.getBoolean(PREF_ENABLE_TAP_DEVICE_DISCOVERY, false);
442
+ }
443
+
444
+ // Cheating and writing an empty record. We may actually be able to erase some tag types.
445
+ private void eraseTag(CallbackContext callbackContext) {
446
+ Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
447
+ NdefRecord[] records = {
448
+ new NdefRecord(NdefRecord.TNF_EMPTY, new byte[0], new byte[0], new byte[0])
449
+ };
450
+ writeNdefMessage(new NdefMessage(records), tag, callbackContext);
451
+ }
452
+
453
+ private void writeTag(JSONArray data, CallbackContext callbackContext) throws JSONException {
454
+ if (getIntent() == null) { // TODO remove this and handle LostTag
455
+ callbackContext.error("Failed to write tag, received null intent");
456
+ }
457
+
458
+ Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
459
+ NdefRecord[] records = Util.jsonToNdefRecords(data.getString(0));
460
+ writeNdefMessage(new NdefMessage(records), tag, callbackContext);
461
+ }
462
+
463
+ private void writeNdefMessage(final NdefMessage message, final Tag tag,
464
+ final CallbackContext callbackContext) {
465
+ cordova.getThreadPool().execute(() -> {
466
+ try {
467
+ Ndef ndef = Ndef.get(tag);
468
+ if (ndef != null) {
469
+ ndef.connect();
470
+
471
+ if (ndef.isWritable()) {
472
+ int size = message.toByteArray().length;
473
+ if (ndef.getMaxSize() < size) {
474
+ callbackContext.error("Tag capacity is " + ndef.getMaxSize() +
475
+ " bytes, message is " + size + " bytes.");
476
+ } else {
477
+ ndef.writeNdefMessage(message);
478
+ callbackContext.success();
479
+ }
480
+ } else {
481
+ callbackContext.error("Tag is read only");
482
+ }
483
+ ndef.close();
484
+ } else {
485
+ NdefFormatable formatable = NdefFormatable.get(tag);
486
+ if (formatable != null) {
487
+ formatable.connect();
488
+ formatable.format(message);
489
+ callbackContext.success();
490
+ formatable.close();
491
+ } else {
492
+ callbackContext.error("Tag doesn't support NDEF");
493
+ }
494
+ }
495
+ } catch (FormatException e) {
496
+ callbackContext.error(e.getMessage());
497
+ } catch (TagLostException e) {
498
+ callbackContext.error(e.getMessage());
499
+ } catch (IOException e) {
500
+ callbackContext.error(e.getMessage());
501
+ }
502
+ });
503
+ }
504
+
505
+ private void showSettings(CallbackContext callbackContext) {
506
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
507
+ Intent intent = new Intent(android.provider.Settings.ACTION_NFC_SETTINGS);
508
+ getActivity().startActivity(intent);
509
+ } else {
510
+ Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
511
+ getActivity().startActivity(intent);
512
+ }
513
+ callbackContext.success();
514
+ }
515
+
516
+ private void createPendingIntent() {
517
+ if (pendingIntent == null) {
518
+ Activity activity = getActivity();
519
+ Intent intent = new Intent(activity, activity.getClass());
520
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
521
+ int pendingIntentFlags = 0;
522
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
523
+ pendingIntentFlags = PendingIntent.FLAG_MUTABLE;
524
+ }
525
+ pendingIntent = PendingIntent.getActivity(activity, 0, intent, pendingIntentFlags);
526
+ }
527
+ }
528
+
529
+ private void addTechList(String[] list) {
530
+ this.addTechFilter();
531
+ this.addToTechList(list);
532
+ }
533
+
534
+ private void removeTechList(String[] list) {
535
+ this.removeTechFilter();
536
+ this.removeFromTechList(list);
537
+ }
538
+
539
+ private void addTechFilter() {
540
+ intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED));
541
+ }
542
+
543
+ private void removeTechFilter() {
544
+ Iterator<IntentFilter> iterator = intentFilters.iterator();
545
+ while (iterator.hasNext()) {
546
+ IntentFilter intentFilter = iterator.next();
547
+ if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intentFilter.getAction(0))) {
548
+ iterator.remove();
549
+ }
550
+ }
551
+ }
552
+
553
+ private void addTagFilter() {
554
+ intentFilters.add(new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED));
555
+ }
556
+
557
+ private void removeTagFilter() {
558
+ Iterator<IntentFilter> iterator = intentFilters.iterator();
559
+ while (iterator.hasNext()) {
560
+ IntentFilter intentFilter = iterator.next();
561
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intentFilter.getAction(0))) {
562
+ iterator.remove();
563
+ }
564
+ }
565
+ }
566
+
567
+ private void restartNfc() {
568
+ stopNfc();
569
+ startNfc();
570
+ }
571
+
572
+ private void startNfc() {
573
+ createPendingIntent(); // onResume can call startNfc before execute
574
+
575
+ getActivity().runOnUiThread(() -> {
576
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
577
+
578
+ if (nfcAdapter != null && !getActivity().isFinishing()) {
579
+ try {
580
+ IntentFilter[] intentFilters = getIntentFilters();
581
+ String[][] techLists = getTechLists();
582
+ // don't start NFC unless some intent filters or tech lists have been added,
583
+ // because empty lists act as wildcards and receives ALL scan events
584
+ if (intentFilters.length > 0 || techLists.length > 0) {
585
+ nfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), intentFilters, techLists);
586
+ }
587
+
588
+ } catch (IllegalStateException e) {
589
+ // issue 110 - user exits app with home button while nfc is initializing
590
+ Log.w(TAG, "Illegal State Exception starting NFC. Assuming application is terminating.");
591
+ }
592
+
593
+ }
594
+ });
595
+ }
596
+
597
+ private void stopNfc() {
598
+ Log.d(TAG, "stopNfc");
599
+ getActivity().runOnUiThread(() -> {
600
+
601
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
602
+
603
+ if (nfcAdapter != null) {
604
+ try {
605
+ nfcAdapter.disableForegroundDispatch(getActivity());
606
+ } catch (IllegalStateException e) {
607
+ // issue 125 - user exits app with back button while nfc
608
+ Log.w(TAG, "Illegal State Exception stopping NFC. Assuming application is terminating.");
609
+ }
610
+ }
611
+ });
612
+ }
613
+
614
+ private void addToTechList(String[] techs) {
615
+ techLists.add(techs);
616
+ }
617
+
618
+ private void removeFromTechList(String[] techs) {
619
+ Iterator<String[]> iterator = techLists.iterator();
620
+ while (iterator.hasNext()) {
621
+ String[] list = iterator.next();
622
+ if (Arrays.equals(list, techs)) {
623
+ iterator.remove();
624
+ }
625
+ }
626
+ }
627
+
628
+ private void removeIntentFilter(String mimeType) {
629
+ Iterator<IntentFilter> iterator = intentFilters.iterator();
630
+ while (iterator.hasNext()) {
631
+ IntentFilter intentFilter = iterator.next();
632
+ String mt = intentFilter.getDataType(0);
633
+ if (mimeType.equals(mt)) {
634
+ iterator.remove();
635
+ }
636
+ }
637
+ }
638
+
639
+ private IntentFilter createIntentFilter(String mimeType) throws MalformedMimeTypeException {
640
+ IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
641
+ intentFilter.addDataType(mimeType);
642
+ return intentFilter;
643
+ }
644
+
645
+ private PendingIntent getPendingIntent() {
646
+ return pendingIntent;
647
+ }
648
+
649
+ private IntentFilter[] getIntentFilters() {
650
+ return intentFilters.toArray(new IntentFilter[intentFilters.size()]);
651
+ }
652
+
653
+ private String[][] getTechLists() {
654
+ //noinspection ToArrayCallWithZeroLengthArrayArgument
655
+ return techLists.toArray(new String[0][0]);
656
+ }
657
+
658
+ private void parseMessage() {
659
+ cordova.getThreadPool().execute(() -> {
660
+ try {
661
+ Log.d(TAG, "parseMessage " + getIntent());
662
+ Intent intent = getIntent();
663
+ String action = intent.getAction();
664
+ Log.d(TAG, "action " + action);
665
+ if (action == null) {
666
+ return;
667
+ }
668
+
669
+ final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
670
+ if (tag != null) {
671
+ Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
672
+
673
+
674
+ if (isTapDeviceDiscoveryEnabled() && isIoTizeTag(tag)) {
675
+ onTapDeviceDiscoveredIntent(intent);
676
+ }
677
+
678
+ if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
679
+ Ndef ndef = Ndef.get(tag);
680
+ fireNdefEvent(NDEF_MIME, ndef, messages);
681
+ savedIntent = intent;
682
+ savedIntentTime = System.currentTimeMillis();
683
+ fireTagEvent(tag, messages);
684
+
685
+ } else if (action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
686
+ this._fireTagEventsFromTechList(tag, messages);
687
+ fireTagEvent(tag, messages);
688
+ } else if (action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
689
+ fireTagEvent(tag, messages);
690
+ }
691
+ }
692
+ }
693
+ catch (RuntimeException err) {
694
+ Log.w(TAG, "Unhandled error with parseMessage()", err);
695
+ }
696
+
697
+ setIntent(new Intent());
698
+ });
699
+ }
700
+
701
+ private void _fireTagEventsFromTechList(@NonNull Tag tag, @NonNull Parcelable[] messages) {
702
+ for (String tagTech : tag.getTechList()) {
703
+ Log.d(TAG, tagTech);
704
+ if (tagTech.equals(NdefFormatable.class.getName())) {
705
+ fireNdefFormatableEvent(tag);
706
+ } else if (tagTech.equals(Ndef.class.getName())) { //
707
+ this._fireNdefEvent(tag, messages);
708
+ }
709
+ }
710
+ }
711
+
712
+ private void _fireNdefEvent(@NonNull Tag tag, @NonNull Parcelable[] messages) {
713
+ Ndef ndef = Ndef.get(tag);
714
+ fireNdefEvent(NDEF, ndef, messages);
715
+ }
716
+
717
+ private boolean isIoTizeTag(@Nullable Tag tag) {
718
+ if (tag == null) {
719
+ return false;
720
+ }
721
+ boolean hasNfcV = Arrays.stream(tag.getTechList()).anyMatch(s -> s.endsWith("NfcV"));
722
+ if (!hasNfcV) {
723
+ return false;
724
+ }
725
+ Ndef ndef = Ndef.get(tag);
726
+ if (ndef == null) {
727
+ return false;
728
+ }
729
+ NdefMessage ndefMessages = ndef.getCachedNdefMessage();
730
+ if (ndefMessages == null) {
731
+ return false;
732
+ }
733
+ NdefRecord[] records = ndefMessages.getRecords();
734
+
735
+ return records.length >= 4; // TODO improve condition
736
+ }
737
+
738
+ private void sendEvent(String type, JSONObject tag, JSONObject tap) {
739
+ try {
740
+ JSONObject event = new JSONObject();
741
+ event.put("type", type); // TAG_DEFAULT, NDEF, NDEF_MIME, NDEF_FORMATABLE
742
+ event.put("tag", tag); // JSON representing the NFC tag and NDEF messages
743
+ event.put("tap", tap);
744
+ sendEvent(event);
745
+ } catch (JSONException e) {
746
+ Log.e(TAG, "Error sending NFC event through the channel", e);
747
+ }
748
+
749
+ }
750
+
751
+ private void sendEvent(String type, JSONObject tag) {
752
+ try {
753
+ JSONObject event = new JSONObject();
754
+ event.put("type", type); // TAG_DEFAULT, NDEF, NDEF_MIME, NDEF_FORMATABLE
755
+ event.put("tag", tag); // JSON representing the NFC tag and NDEF messages
756
+ sendEvent(event);
757
+ } catch (JSONException e) {
758
+ Log.e(TAG, "Error sending NFC event through the channel", e);
759
+ }
760
+ }
761
+
762
+ // Send the event data through a channel so the JavaScript side can fire the event
763
+ private void sendEvent(JSONObject event) {
764
+ PluginResult result = new PluginResult(PluginResult.Status.OK, event);
765
+ result.setKeepCallback(true);
766
+ if (channelCallback != null) {
767
+ channelCallback.sendPluginResult(result);
768
+ }
769
+ }
770
+
771
+ private void fireNdefEvent(String type, Ndef ndef, Parcelable[] messages) {
772
+ try {
773
+ JSONObject json = buildNdefJSON(ndef, messages);
774
+ sendEvent(type, json);
775
+ } catch (Throwable e) {
776
+ Log.w(TAG, "Failed to fire NDef event", e);
777
+ }
778
+ }
779
+
780
+ private void fireTapDeviceEvent(IoTizeDevice tap, Intent intent) {
781
+ try {
782
+ Log.d(TAG, "fireTapDeviceEvent " + tap);
783
+ Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
784
+ Ndef ndef = Ndef.get(tag);
785
+ Parcelable[] messages = intent.getParcelableArrayExtra((NfcAdapter.EXTRA_NDEF_MESSAGES));
786
+
787
+ sendEvent(NFC_TAP_DEVICE, buildNdefJSON(ndef, messages), buildTapJSON(tap));
788
+ } catch (JSONException e) {
789
+ Log.e(TAG, e.getMessage(), e);
790
+ } catch (Throwable e) {
791
+ Log.w(TAG, "Failed to fire Tap Device event", e);
792
+ }
793
+ }
794
+
795
+ private JSONObject buildTapJSON(IoTizeDevice tap) throws JSONException {
796
+ JSONObject tapInfo = new JSONObject();
797
+ tapInfo.put("nfcPairingDone", true); // TODO
798
+ JSONObject encryptionJSON = new JSONObject();
799
+ boolean encryptionEnabled = false;
800
+ if (tap.isEncryptionEnabled()) {
801
+ EncryptionAlgo encryptionAlgo = tap.getClient().getEncryptionAlgo();
802
+ if (encryptionAlgo != null) {
803
+ encryptionEnabled = true;
804
+ encryptionJSON.put("enabled", true);
805
+ JSONObject keysOptions = new JSONObject();
806
+ keysOptions.put("sessionKey", Util.byteArrayToJSON(encryptionAlgo.getKey()));
807
+ int frameCounter = 0;
808
+ try {
809
+ // TODO change with frameCounter
810
+ TapClient client = tap.getClient();
811
+ Field field = client.getClass().getDeclaredField("frameCounter");
812
+ field.setAccessible(true);
813
+ frameCounter = (int) field.get(client);
814
+ } catch (NoSuchFieldException e) {
815
+ Log.w(TAG, e.getMessage(), e);
816
+ } catch (IllegalAccessException e) {
817
+ Log.w(TAG, e.getMessage(), e);
818
+ }
819
+ keysOptions.put("sessionKeyHex", Helper.ByteArrayToHexString(encryptionAlgo.getKey()));
820
+ // keysOptions.put("ivEncode", Util.byteArrayToJSON(());
821
+ // keysOptions.put("ivDecode", Util.byteArrayToJSON(());
822
+
823
+ encryptionJSON.put("keys", keysOptions);
824
+ encryptionJSON.put("frameCounter", frameCounter);
825
+ }
826
+ }
827
+ encryptionJSON.put("enabled", encryptionEnabled);
828
+ tapInfo.put("encryption", encryptionJSON);
829
+ return tapInfo;
830
+ }
831
+
832
+ private void fireNdefFormatableEvent(Tag tag) {
833
+ sendEvent(NDEF_FORMATABLE, Util.tagToJSON(tag));
834
+ }
835
+
836
+ private void fireTagEvent(Tag tag, Parcelable[] messages) {
837
+ if (Arrays.asList(tag.getTechList()).contains(Ndef.class.getName())) {
838
+ sendEvent(TAG_DEFAULT, buildNdefJSON(Ndef.get(tag), messages));
839
+ }
840
+ else {
841
+ sendEvent(TAG_DEFAULT, Util.tagToJSON(tag));
842
+ }
843
+ }
844
+
845
+ /**
846
+ * May throw a java.lang.SecurityException error if Tag is out of date (tested on Android 13)
847
+ */
848
+ private JSONObject buildNdefJSON(Ndef ndef, Parcelable[] messages) throws SecurityException {
849
+
850
+ JSONObject json = Util.ndefToJSON(ndef);
851
+
852
+ // ndef is null for peer-to-peer
853
+ // ndef and messages are null for ndef format-able
854
+ if (ndef == null && messages != null) {
855
+
856
+ try {
857
+
858
+ if (messages.length > 0) {
859
+ NdefMessage message = (NdefMessage) messages[0];
860
+ json.put("ndefMessage", Util.messageToJSON(message));
861
+ // guessing type, would prefer a more definitive way to determine type
862
+ json.put("type", "NDEF Push Protocol");
863
+ }
864
+
865
+ if (messages.length > 1) {
866
+ Log.wtf(TAG, "Expected one ndefMessage but found " + messages.length);
867
+ }
868
+
869
+ } catch (JSONException e) {
870
+ // shouldn't happen
871
+ Log.e(Util.TAG, "Failed to convert ndefMessage into json", e);
872
+ }
873
+ }
874
+ return json;
875
+ }
876
+
877
+ private boolean recycledIntent() { // TODO this is a kludge, find real solution
878
+
879
+ int flags = getIntent().getFlags();
880
+ if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) {
881
+ Log.i(TAG, "Launched from history, killing recycled intent");
882
+ setIntent(new Intent());
883
+ return true;
884
+ }
885
+ return false;
886
+ }
887
+
888
+ @Override
889
+ public void onStart() {
890
+ super.onStart();
891
+ this.initializeTapDeviceListener();
892
+ }
893
+
894
+ @Override
895
+ public void onPause(boolean multitasking) {
896
+ Log.d(TAG, "onPause " + getIntent());
897
+ super.onPause(multitasking);
898
+ if (multitasking) {
899
+ // nfc can't run in background
900
+ stopNfc();
901
+ }
902
+ }
903
+
904
+ @Override
905
+ public void onResume(boolean multitasking) {
906
+ Log.d(TAG, "onResume " + getIntent());
907
+ super.onResume(multitasking);
908
+ startNfc();
909
+ }
910
+
911
+ @Override
912
+ public void onNewIntent(Intent intent) {
913
+ Log.d(TAG, "onNewIntent " + intent);
914
+ super.onNewIntent(intent);
915
+ setIntent(intent);
916
+ savedIntent = intent;
917
+ parseMessage();
918
+ }
919
+
920
+ private Activity getActivity() {
921
+ return this.cordova.getActivity();
922
+ }
923
+
924
+ private Intent getIntent() {
925
+ return getActivity().getIntent();
926
+ }
927
+
928
+ private void setIntent(Intent intent) {
929
+ getActivity().setIntent(intent);
930
+ }
931
+
932
+ /**
933
+ * Enable I/O operations to the tag from this TagTechnology object.
934
+ * *
935
+ *
936
+ * @param tech TagTechnology class name e.g. 'android.nfc.tech.IsoDep' or 'android.nfc.tech.NfcV'
937
+ * @param timeout tag timeout
938
+ * @param callbackContext Cordova callback context
939
+ */
940
+ private void connectRaw(final String tech, final int timeout,
941
+ final CallbackContext callbackContext) {
942
+ final String fullTechName = !tech.startsWith(ANDROID_NFC_TECH_CLASS_PASS) ? ANDROID_NFC_TECH_CLASS_PASS + tech : tech;
943
+ this.cordova.getThreadPool().execute(() -> {
944
+ try {
945
+ this._lastTechName = fullTechName;
946
+ this._initIntentTag(fullTechName);
947
+
948
+ if (tagTechnology == null) {
949
+ callbackContext.error("Tag does not support " + tech);
950
+ return;
951
+ }
952
+
953
+ if (!tagTechnology.isConnected()) {
954
+ tagTechnology.connect();
955
+ }
956
+ setTimeout(timeout);
957
+ Log.d(TAG, "NFC Connection successful");
958
+ callbackContext.success();
959
+ } catch (IOException ex) {
960
+ Log.e(TAG, "Tag connection failed", ex);
961
+ callbackContext.error("Tag connection failed");
962
+ } catch (Throwable e) {
963
+ Log.e(TAG, e.getMessage(), e);
964
+ callbackContext.error(e.getMessage());
965
+ }
966
+ });
967
+ }
968
+
969
+ /**
970
+ * Perform Tap NFCProtocol connect call
971
+ *
972
+ * @param tech TagTechnology class name e.g. 'android.nfc.tech.IsoDep' or 'android.nfc.tech.NfcV'
973
+ * @param timeout tag timeout
974
+ * @param callbackContext Cordova callback context
975
+ */
976
+ private void connectTap(final String tech, final int timeout,
977
+ final CallbackContext callbackContext) {
978
+ final String fullTechName = !tech.startsWith(ANDROID_NFC_TECH_CLASS_PASS) ? ANDROID_NFC_TECH_CLASS_PASS + tech : tech;
979
+ this.cordova.getThreadPool().execute(() -> {
980
+ try {
981
+ this._lastTechName = fullTechName;
982
+ this._initIntentTag(fullTechName);
983
+
984
+ if (nfcProtocol == null) {
985
+ callbackContext.error("Tag does not support " + tech);
986
+ return;
987
+ }
988
+
989
+ nfcProtocol.connect();
990
+ setTimeout(timeout);
991
+ Log.d(TAG, "NFC Connection successful");
992
+ callbackContext.success();
993
+ } catch (IOException ex) {
994
+ Log.e(TAG, "Tag connection failed", ex);
995
+ callbackContext.error("Tag connection failed");
996
+ } catch (Throwable e) {
997
+ Log.e(TAG, e.getMessage(), e);
998
+ callbackContext.error(e.getMessage());
999
+ }
1000
+ });
1001
+ }
1002
+
1003
+ private void _initIntentTag(final String tech) throws Exception {
1004
+ Intent intent = getIntent();
1005
+ Tag tag = null;
1006
+ if (intent != null) {
1007
+ tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1008
+ }
1009
+ if (tag == null && savedIntent != null) {
1010
+ tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1011
+ }
1012
+
1013
+ if (tag == null) {
1014
+ Log.e(TAG, "No Tag");
1015
+ throw new Exception("No Tag");
1016
+ }
1017
+
1018
+ // get technologies supported by this tag
1019
+ List<String> techList = Arrays.asList(tag.getTechList());
1020
+ if (!techList.contains(tech)) {
1021
+ throw new Exception("Tech " + tech + " not available");
1022
+ }
1023
+ // use reflection to call the static function Tech.get(tag)
1024
+ tagTechnologyClass = Class.forName(tech);
1025
+ nfcProtocol = NFCProtocol.create(tag);
1026
+ Method method = tagTechnologyClass.getMethod("get", Tag.class);
1027
+ tagTechnology = (TagTechnology) method.invoke(null, tag);
1028
+ if (tagTechnology == null) {
1029
+ Log.e(TAG, "No Tag Technology");
1030
+ throw new Exception("No Tag");
1031
+ }
1032
+ }
1033
+
1034
+ private void setTimeout(int timeout) {
1035
+ if (timeout < 0) {
1036
+ return;
1037
+ }
1038
+ if (nfcProtocol != null) {
1039
+ nfcProtocol.getConfiguration().connectionTimeoutMillis = timeout;
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Disable I/O operations to the tag from this TagTechnology object, and release resources.
1045
+ *
1046
+ * @param callbackContext Cordova callback context
1047
+ */
1048
+ private void close(CallbackContext callbackContext) {
1049
+ cordova.getThreadPool().execute(() -> {
1050
+ try {
1051
+ _lastTechName = DEFAULT_NFC_TECH_CLASS_PASS;
1052
+ if (nfcProtocol != null && nfcProtocol.isConnected()) {
1053
+ nfcProtocol.disconnect();
1054
+ }
1055
+ callbackContext.success();
1056
+
1057
+ } catch (Exception ex) {
1058
+ Log.e(TAG, "Error closing nfc connection", ex);
1059
+ callbackContext.error("Error closing nfc connection " + ex.getLocalizedMessage());
1060
+ }
1061
+ finally {
1062
+ nfcProtocol = null;
1063
+ tagTechnology = null;
1064
+ tagTechnologyClass = null;
1065
+ }
1066
+ });
1067
+ }
1068
+
1069
+ /**
1070
+ * Send raw commands to the tag and receive the response.
1071
+ *
1072
+ * @param data byte[] command to be passed to the tag
1073
+ * @param callbackContext Cordova callback context
1074
+ */
1075
+ private void transceiveRaw(final byte[] data, final CallbackContext callbackContext) {
1076
+ cordova.getThreadPool().execute(() -> {
1077
+ try {
1078
+ this._connectIntentTagIfNeeded();
1079
+ Method transceiveMethod = tagTechnologyClass.getMethod("transceive", byte[].class);
1080
+ try {
1081
+ @SuppressWarnings("PrimitiveArrayArgumentToVarargsMethod")
1082
+ byte[] response = (byte[]) transceiveMethod.invoke(tagTechnology, data);
1083
+ callbackContext.success(Helper.ByteArrayToHexString(response));
1084
+ }
1085
+ catch (InvocationTargetException e) {
1086
+ Throwable targetException = e.getTargetException();
1087
+ String errorMessage = targetException.getMessage();
1088
+ if (errorMessage != null && (errorMessage.endsWith("is out of date") ||
1089
+ errorMessage.contains("Call connect() first"))) {
1090
+ this._connectIntentTag();
1091
+ // Retry
1092
+ byte[] response = (byte[]) transceiveMethod.invoke(tagTechnology, data);
1093
+ callbackContext.success(Helper.ByteArrayToHexString(response));
1094
+ }
1095
+ else {
1096
+ throw e;
1097
+ }
1098
+ }
1099
+ }
1100
+ catch (InvocationTargetException e) {
1101
+ String msg = e.getTargetException().getMessage();
1102
+ Log.e(TAG, msg, e);
1103
+ callbackContext.error(msg);
1104
+ }
1105
+ catch (Throwable e) {
1106
+ Log.e(TAG, e.getMessage(), e);
1107
+ callbackContext.error(e.getMessage());
1108
+ }
1109
+ });
1110
+ }
1111
+
1112
+ private void _connectIntentTag() throws Exception {
1113
+ this._initIntentTag(this._lastTechName);
1114
+ tagTechnology.connect();
1115
+ }
1116
+
1117
+ private void _connectIntentTagIfNeeded() throws Exception {
1118
+ if (tagTechnology == null) {
1119
+ this._initIntentTag(this._lastTechName);
1120
+ tagTechnology.connect();
1121
+ }
1122
+ }
1123
+
1124
+ /**
1125
+ * Send raw commands to the tag and receive the response.
1126
+ *
1127
+ * @param data byte[] command to be passed to the tag
1128
+ * @param callbackContext Cordova callback context
1129
+ */
1130
+ private void transceiveTap(final byte[] data, final CallbackContext callbackContext) {
1131
+ cordova.getThreadPool().execute(() -> {
1132
+ try {
1133
+ if (nfcProtocol == null) {
1134
+ Log.e(TAG, "No Tech");
1135
+ callbackContext.error("No Tech");
1136
+ return;
1137
+ }
1138
+ if (!nfcProtocol.isConnected()) {
1139
+ Log.e(TAG, "Not connected");
1140
+ callbackContext.error("Not connected");
1141
+ return;
1142
+ }
1143
+ byte[] response = nfcProtocol.send(data);
1144
+ callbackContext.success(Helper.ByteArrayToHexString(response));
1145
+ } catch (Exception e) {
1146
+ Log.e(TAG, e.getMessage(), e);
1147
+ callbackContext.error(e.getMessage());
1148
+ }
1149
+ });
1150
+ }
1151
+ }