@capgo/capacitor-nfc 7.0.10 → 7.1.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.
|
@@ -10,8 +10,10 @@ import android.nfc.NdefMessage;
|
|
|
10
10
|
import android.nfc.NdefRecord;
|
|
11
11
|
import android.nfc.NfcAdapter;
|
|
12
12
|
import android.nfc.Tag;
|
|
13
|
+
import android.nfc.tech.MifareUltralight;
|
|
13
14
|
import android.nfc.tech.Ndef;
|
|
14
15
|
import android.nfc.tech.NdefFormatable;
|
|
16
|
+
import android.nfc.tech.NfcA;
|
|
15
17
|
import android.os.Build;
|
|
16
18
|
import android.os.Bundle;
|
|
17
19
|
import android.provider.Settings;
|
|
@@ -356,21 +358,214 @@ public class CapacitorNfcPlugin extends Plugin {
|
|
|
356
358
|
return;
|
|
357
359
|
}
|
|
358
360
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
NdefMessage message = null;
|
|
362
|
+
String[] techList = tag.getTechList();
|
|
363
|
+
|
|
364
|
+
// Check tech list first - if MIFARE Ultralight is present, prioritize it
|
|
365
|
+
// (tags can become stale very quickly, so we need to read immediately)
|
|
366
|
+
boolean hasMifareUltralight = Arrays.asList(techList).contains("android.nfc.tech.MifareUltralight");
|
|
367
|
+
|
|
368
|
+
if (hasMifareUltralight) {
|
|
369
|
+
// Try MIFARE Ultralight first - read immediately before tag becomes stale
|
|
370
|
+
MifareUltralight mifare = MifareUltralight.get(tag);
|
|
371
|
+
if (mifare != null) {
|
|
372
|
+
message = readNdefFromMifareUltralight(mifare);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// If no message from MIFARE, try standard NDEF
|
|
377
|
+
if (message == null) {
|
|
378
|
+
Ndef ndef = Ndef.get(tag);
|
|
379
|
+
if (ndef != null) {
|
|
380
|
+
// First try to get cached message (fast path)
|
|
381
|
+
try {
|
|
382
|
+
message = ndef.getCachedNdefMessage();
|
|
383
|
+
} catch (Exception ex) {
|
|
384
|
+
// Ignore - will try to read directly
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// If no cached message, read it synchronously while tag is in range
|
|
388
|
+
if (message == null) {
|
|
389
|
+
try {
|
|
390
|
+
ndef.connect();
|
|
391
|
+
message = ndef.getNdefMessage();
|
|
392
|
+
ndef.close();
|
|
393
|
+
} catch (IOException | FormatException ex) {
|
|
394
|
+
try {
|
|
395
|
+
ndef.close();
|
|
396
|
+
} catch (IOException closeEx) {
|
|
397
|
+
// Ignore close errors
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
366
401
|
}
|
|
367
402
|
}
|
|
368
403
|
|
|
369
404
|
lastTag.set(tag);
|
|
370
|
-
lastMessage.set(
|
|
405
|
+
lastMessage.set(message);
|
|
406
|
+
emitTagEvent(tag, message);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Reads NDEF message from MIFARE Ultralight tag by reading raw pages.
|
|
411
|
+
*
|
|
412
|
+
* Based on NFC Forum Type 2 Tag Operation specification:
|
|
413
|
+
* - NDEF data is stored in TLV (Type-Length-Value) format starting at page 4
|
|
414
|
+
* - TLV Type 0x03 indicates NDEF message
|
|
415
|
+
* - Length encoding: single byte if < 0xFF, or 0xFF + 2-byte length if >= 0xFF
|
|
416
|
+
*
|
|
417
|
+
* References:
|
|
418
|
+
* - Android MifareUltralight API: https://developer.android.com/reference/android/nfc/tech/MifareUltralight
|
|
419
|
+
* - NFC Forum Type 2 Tag Operation specification
|
|
420
|
+
*/
|
|
421
|
+
private NdefMessage readNdefFromMifareUltralight(MifareUltralight mifare) {
|
|
422
|
+
try {
|
|
423
|
+
// Connect immediately - tag can become stale quickly
|
|
424
|
+
mifare.connect();
|
|
425
|
+
|
|
426
|
+
// Log tag variant for debugging
|
|
427
|
+
int tagType = mifare.getType();
|
|
428
|
+
String variantName;
|
|
429
|
+
switch (tagType) {
|
|
430
|
+
case MifareUltralight.TYPE_ULTRALIGHT:
|
|
431
|
+
variantName = "MIFARE Ultralight (standard, 64 bytes)";
|
|
432
|
+
break;
|
|
433
|
+
case MifareUltralight.TYPE_ULTRALIGHT_C:
|
|
434
|
+
variantName = "MIFARE Ultralight C (up to 192 bytes)";
|
|
435
|
+
break;
|
|
436
|
+
default:
|
|
437
|
+
variantName = "MIFARE Ultralight (type: " + tagType + ", unknown variant)";
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
Log.d(TAG, "MIFARE Ultralight tag variant: " + variantName);
|
|
441
|
+
|
|
442
|
+
// Read pages 4-7 first (readPages reads 4 pages = 16 bytes at a time)
|
|
443
|
+
// This contains the TLV header
|
|
444
|
+
byte[] firstPages = mifare.readPages(4);
|
|
445
|
+
if (firstPages == null || firstPages.length < 4) {
|
|
446
|
+
mifare.close();
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check for NDEF TLV (Type = 0x03 per NFC Forum spec)
|
|
451
|
+
if (firstPages[0] != 0x03) {
|
|
452
|
+
mifare.close();
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Parse TLV length field
|
|
457
|
+
int ndefLength;
|
|
458
|
+
int tlvHeaderSize;
|
|
459
|
+
if ((firstPages[1] & 0xFF) < 0xFF) {
|
|
460
|
+
// Short format: single byte length
|
|
461
|
+
ndefLength = firstPages[1] & 0xFF;
|
|
462
|
+
tlvHeaderSize = 2; // Type (1 byte) + Length (1 byte)
|
|
463
|
+
} else {
|
|
464
|
+
// Extended format: 0xFF + 2-byte length
|
|
465
|
+
if (firstPages.length < 4) {
|
|
466
|
+
mifare.close();
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
ndefLength = ((firstPages[2] & 0xFF) << 8) | (firstPages[3] & 0xFF);
|
|
470
|
+
tlvHeaderSize = 4; // Type (1 byte) + 0xFF (1 byte) + Length (2 bytes)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (ndefLength == 0 || ndefLength > 1024) {
|
|
474
|
+
// Reasonable upper limit
|
|
475
|
+
mifare.close();
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Calculate total bytes needed (TLV header + NDEF data)
|
|
480
|
+
int totalBytesNeeded = tlvHeaderSize + ndefLength;
|
|
481
|
+
int totalPagesNeeded = (totalBytesNeeded + 3) / 4; // Round up to pages
|
|
482
|
+
|
|
483
|
+
// Read all necessary pages sequentially
|
|
484
|
+
// Standard MIFARE Ultralight: pages 4-15 (48 bytes)
|
|
485
|
+
// MIFARE Ultralight EV1: up to 256 pages (1024 bytes)
|
|
486
|
+
// MIFARE Ultralight C: up to 256 pages (1024 bytes)
|
|
487
|
+
byte[] allData = new byte[totalPagesNeeded * 4];
|
|
488
|
+
int bytesRead = 0;
|
|
489
|
+
int currentPage = 4;
|
|
490
|
+
|
|
491
|
+
// Copy first 16 bytes we already read
|
|
492
|
+
int bytesToCopy = Math.min(firstPages.length, allData.length);
|
|
493
|
+
System.arraycopy(firstPages, 0, allData, 0, bytesToCopy);
|
|
494
|
+
bytesRead = bytesToCopy;
|
|
495
|
+
currentPage += 4; // Move to page 8
|
|
496
|
+
|
|
497
|
+
// Read remaining pages if needed
|
|
498
|
+
// Support up to page 256 (MIFARE Ultralight EV1/C maximum)
|
|
499
|
+
while (bytesRead < totalBytesNeeded && currentPage < 260) {
|
|
500
|
+
try {
|
|
501
|
+
byte[] pages = mifare.readPages(currentPage);
|
|
502
|
+
if (pages == null || pages.length == 0) {
|
|
503
|
+
// No more pages available - tag might not support this many pages
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
int bytesNeeded = totalBytesNeeded - bytesRead;
|
|
508
|
+
int bytesToRead = Math.min(pages.length, bytesNeeded);
|
|
509
|
+
System.arraycopy(pages, 0, allData, bytesRead, bytesToRead);
|
|
510
|
+
bytesRead += bytesToRead;
|
|
511
|
+
currentPage += 4; // readPages reads 4 pages at a time
|
|
512
|
+
} catch (IOException e) {
|
|
513
|
+
// Tag might not support reading beyond this page
|
|
514
|
+
// This is normal for tags with fewer pages than needed
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
mifare.close();
|
|
520
|
+
|
|
521
|
+
// Check if we have enough data
|
|
522
|
+
if (bytesRead < totalBytesNeeded) {
|
|
523
|
+
// Incomplete read - tag might not have enough pages or was removed
|
|
524
|
+
Log.w(
|
|
525
|
+
TAG,
|
|
526
|
+
String.format(
|
|
527
|
+
"Incomplete NDEF read: read %d bytes, needed %d bytes (stopped at page %d, variant: %s)",
|
|
528
|
+
bytesRead,
|
|
529
|
+
totalBytesNeeded,
|
|
530
|
+
currentPage,
|
|
531
|
+
variantName
|
|
532
|
+
)
|
|
533
|
+
);
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Extract NDEF data (skip TLV header)
|
|
538
|
+
byte[] ndefData = new byte[ndefLength];
|
|
539
|
+
System.arraycopy(allData, tlvHeaderSize, ndefData, 0, ndefLength);
|
|
540
|
+
|
|
541
|
+
// Parse NDEF message
|
|
542
|
+
try {
|
|
543
|
+
return new NdefMessage(ndefData);
|
|
544
|
+
} catch (FormatException e) {
|
|
545
|
+
Log.w(TAG, "Failed to parse NDEF message from MIFARE Ultralight", e);
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
} catch (SecurityException e) {
|
|
549
|
+
// Tag became stale - this happens if tag is removed or too much time passed
|
|
550
|
+
try {
|
|
551
|
+
mifare.close();
|
|
552
|
+
} catch (Exception closeEx) {
|
|
553
|
+
// Ignore close errors
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
} catch (IOException e) {
|
|
557
|
+
try {
|
|
558
|
+
mifare.close();
|
|
559
|
+
} catch (IOException closeEx) {
|
|
560
|
+
// Ignore close errors
|
|
561
|
+
}
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
371
565
|
|
|
372
|
-
|
|
373
|
-
|
|
566
|
+
private void emitTagEvent(Tag tag, NdefMessage message) {
|
|
567
|
+
JSObject tagJson = NfcJsonConverter.tagToJSObject(tag, message);
|
|
568
|
+
String eventType = determineEventType(tag, message);
|
|
374
569
|
JSObject event = new JSObject();
|
|
375
570
|
event.put("type", eventType);
|
|
376
571
|
event.put("tag", tagJson);
|
package/package.json
CHANGED