android tags nfc mifare ndef

Android NFC-ndef.writeNdefMessage() arroja IOException y borra datos de etiqueta



tags mifare (1)

Realmente me pregunto qué más esperarías que ocurriera cuando eliminas un dispositivo de almacenamiento en medio de sobrescribir sus datos.

¿Por qué mi código (ver a continuación) borra los datos de la etiqueta de esta manera?

Tu código no está realmente "borrando" datos. Simplemente comienza a sobrescribir los datos desde el comienzo de la memoria de etiquetas, dejando la etiqueta en un estado indefinido cuando interrumpe la escritura.

Una etiqueta NFC solo admite el almacenamiento de un mensaje NDEF a la vez. En consecuencia, cuando comienza a escribir un nuevo mensaje NDEF, el viejo mensaje NDEF debe sobrescribirse. Así,

ndef.writeNdefMessage(ndefMessageNew);

sobrescribirá el mensaje NDEF existente comenzando en su primer bloque. Para NTAG203, MIFARE Ultralight y MIFARE Ultralight C (es decir, tres tipos de etiquetas diferentes por cierto), este primer bloque estará alrededor del bloque 4. writeNdefMessage escribirá el nuevo bloque de mensajes para el bloque reemplazando datos viejos con datos nuevos.

Si el procedimiento de escritura se interrumpe (por ejemplo, tirando de la etiqueta desde el campo del lector), solo se escribirán partes del mensaje nuevo (y partes del mensaje anterior pueden permanecer en la etiqueta). Dado que ni el mensaje anterior ni el nuevo están completos, Android (al igual que cualquier otro lector NDEF) no puede leer un mensaje NDEF válido de la etiqueta y, por lo tanto, no detecta ningún mensaje NDEF. La aplicación aún detecta la etiqueta, ya que también se registró para el intento TECH_DISCOVERED (que no requiere que la etiqueta contenga un mensaje NDEF).

¿Cómo puedo solucionar el problema subyacente o existe una solución aceptable?

Si su mensaje NDEF es tan largo que sus usuarios pueden realmente tirar de la etiqueta mientras escriben, no hay mucho que pueda hacer contra el arrastre (salvo que se indique a los usuarios que no lo hagan). Las etiquetas NFC tampoco tienen ningún tipo de protección de extracción inmediata. Es decir, actualmente no hay etiquetas que guarden de manera confiable el antiguo mensaje NDEF hasta que el nuevo mensaje NDEF se haya escrito por completo.

Lo que podrías hacer es almacenar el viejo (o el nuevo) mensaje NDEF (posiblemente asignado a la identificación de la etiqueta) dentro de tu aplicación y dejar que los usuarios reinicien el procedimiento de escritura una vez que falló. Aún así, eso requeriría la cooperación del usuario.

¿Valdría la pena intentar usar NfcA o IsoDep en lugar de Ndef?

Esa podría ser otra opción: no use NDEF para los datos críticos, sino que use un diseño de memoria específico de la aplicación en su lugar (o además de NDEF). NTAG / MIFARE Ultralight tienen un conjunto de comandos en la parte superior de ISO 14443-3A (NFC-A) y no son compatibles con ISO-DEP (ISO 14443-4). Por lo tanto, podría usar NfcA (o MifareUltralight ) para leer directamente / escribir en las etiquetas usando comandos de bajo nivel. Puede estructurar la memoria de etiquetas en dos secciones que utiliza para almacenar los datos antiguos y nuevos:

Block x: Flag indicating which section (1 or 2) contains the valid data Block x+1: First block of section 1 Block x+2: Second block of section 1 [...] Block x+m: Last block of section 1 Block x+m+1: First block of section 2 Block x+m+2: Second block of section 2 [...] Block x+2*m: Last block of section 2

Donde x es el primer bloque de su estructura de memoria personalizada (incluso podría comenzar esa área después de algún mensaje NDEF fijo) m es la longitud de cada sección en bloques (1 bloque en NTAG / MF Ultralight tiene 4 bytes).

A continuación, utilizaría algo como esto para leer y actualizar su etiqueta:

  1. Lea del bloque x para averiguar qué sección contiene los datos más recientes (más recientes) -> sección s .
  2. Lea los datos de la sección s y úselos como datos actuales.
  3. Escriba los datos nuevos en la otra sección (si s = 1: sección 0, si s = 0: sección 1).
  4. Si los datos se escribieron con éxito (y completamente), actualice el bloque x con el nuevo número de sección.

Los comandos de lectura y escritura de bajo nivel se ven así:

  • LEER:

    byte[] result = nfcA.transceive(new byte[] { (byte)0x30, // READ (byte)(blockNumber & 0x0ff) });

  • ESCRIBIR:

    byte[] result = nfcA.transceive(new byte[] { (byte)0xA2, // WRITE (byte)(blockNumber & 0x0ff), byte0, byte1, byte2, byte3 });

Mi aplicación utiliza el sistema de distribución en primer plano para permitir que un usuario toque su etiqueta NFC para realizar una operación de lectura y escritura en la etiqueta.

Funciona muy bien si el usuario toca su etiqueta correctamente (es decir, la tocan en el lugar correcto del teléfono y la dejan conectada durante el tiempo suficiente), pero si la eliminan físicamente demasiado pronto, entonces ndef.writeNdefMessage(...) lanza una IOException.

Eso significa que la operación de escritura falla, lo cual es bastante justo. ¡Pero el verdadero problema es que la misma operación fallida también elimina todo el formato / mensaje ndef de la etiqueta!

Mi código se basa en los fragmentos del Advanced NFC | Página de Desarrolladores de Android (como desafortunadamente, el enlace al ejemplo de ForegroundDispatch parece estar roto y no hay tal proyecto de muestra para importar a Android Studio).

Paso 1. Aquí está la salida logcat / stacktrace cuando el usuario toca su etiqueta NFC por primera vez, pero la mueve demasiado pronto:

03-28 20:15:18.589 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error java.io.IOException at android.nfc.tech.Ndef.writeNdefMessage(Ndef.java:320) at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:170) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224) at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946) at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959) at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968) at android.app.ActivityThread.access$1700(ActivityThread.java:181) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6145) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) 03-28 20:15:18.599 1481-17792/? E/SecNfcJni: nfaConnectionCallback: NFA_SELECT_RESULT_EVT error: status = 3 03-28 20:15:18.599 1481-1502/? E/SecNfcJni: reSelect: tag is not active

Paso 2. Luego, el mismo usuario vuelve a tocar la misma etiqueta, pero parece que ya no contiene un mensaje ndef (que he confirmado alterando el código y comprobando que ndef.getCachedNdefMessage() devuelve null ):

03-28 20:15:27.499 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error java.lang.Exception: Tag was not ndef formatted: android.nfc.action.TECH_DISCOVERED at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:124) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224) at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946) at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959) at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968) at android.app.ActivityThread.access$1700(ActivityThread.java:181) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6145) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

Estoy teniendo este problema con ambos dispositivos con los que he probado hasta ahora: un Samsung Galaxy Core Prime (un teléfono de gama más baja) con Android 5.1.1 y un Samsung Galaxy A5 (un teléfono de gama media) con Android 5.0.2.

Las etiquetas NFC que usa mi aplicación contienen información importante (es decir, borrar datos sin querer no es una opción), así que mis preguntas son ...

  1. ¿Por qué mi código (ver a continuación) borra los datos de la etiqueta de esta manera?
  2. ¿Cómo puedo solucionar el problema subyacente o existe una solución aceptable?
  3. ¿Valdría la pena intentar usar NfcA o IsoDep en lugar de Ndef?

Después de haber investigado mucho, estoy muy sorprendido de que este problema no haya sido discutido en otra parte, así que si el problema no tiene que ver con mi código, ¿podría ser con las etiquetas NFC que estoy usando? .

Las etiquetas que estoy usando son NXP MIFARE Ultralight (Ultralight C) - NTAG203 (Tipo de etiqueta: ISO 14443-3A). Algunos de estos los compré en eBay, y algunos los compré en Rapid NFC (una compañía de buena reputación), pero parece que tengo este problema con todos ellos.

Aquí está mi código completo para la actividad:

package com.example.exampleapp; import android.app.PendingIntent; import android.content.Intent; import android.content.IntentFilter; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.Ndef; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.Toast; public class NfcTestActivity extends AppCompatActivity { private static String LOG_TAG = NfcTestActivity.class.getSimpleName(); private static int SUCCESS_COUNT = 0; private static int FAILURE_COUNT = 0; private NfcAdapter nfcAdapter; private PendingIntent pendingIntent; private IntentFilter[] intentFiltersArray; private String[][] techListsArray; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_nfc_test); getSupportActionBar().setDisplayShowHomeEnabled(true); nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter == null) { makeToast("NFC not available!", Toast.LENGTH_LONG); finish(); } else { //makeToast("NFC available"); pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("*/*"); /* Handles all MIME based dispatches. You should specify only the ones that you need. */ } catch (IntentFilter.MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } intentFiltersArray = new IntentFilter[]{ ndef }; techListsArray = new String[][]{ new String[]{ Ndef.class.getName() } }; } } @Override public void onPause() { super.onPause(); if (nfcAdapter != null) { nfcAdapter.disableForegroundDispatch(this); } } @Override public void onResume() { super.onResume(); if (nfcAdapter != null) { nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray); } } public void onNewIntent(Intent intent) { Ndef ndef = null; try { String action = intent.getAction(); //makeToast("action: " + action); if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { throw new Exception("Tag was not ndef formatted: " + action); // line #124 } else { Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //do something with tagFromIntent ndef = Ndef.get(tag); //makeToast("ndef: " + ndef); if (ndef == null) { throw new Exception("ndef == null!"); } else { // Connect ndef.connect(); // Get cached message NdefMessage ndefMessageOld = ndef.getCachedNdefMessage(); if (ndefMessageOld == null) { throw new Exception("No ndef message on tag!"); } else { // Get old records NdefRecord[] ndefRecordsOld = ndefMessageOld.getRecords(); int numRecords = (ndefRecordsOld == null) ? 0 : ndefRecordsOld.length; // Create/copy ''new'' records NdefRecord[] ndefRecordsNew = new NdefRecord[numRecords]; for (int i = 0; i < numRecords; i++) { ndefRecordsNew[i] = ndefRecordsOld[i]; } // Create new message NdefMessage ndefMessageNew = new NdefMessage(ndefRecordsNew); // Write new message ndef.writeNdefMessage(ndefMessageNew); // line #170 SUCCESS_COUNT++; // Report success String msg = "Read & wrote " + numRecords + " records."; makeToast(msg); Log.d(LOG_TAG, msg); } } } } catch(Exception e) { FAILURE_COUNT++; Log.e(LOG_TAG, "Tag error", e); makeToast("Tag error: " + e, Toast.LENGTH_LONG); } finally { try { if (ndef != null) { ndef.close(); } } catch(Exception e) { Log.e(LOG_TAG, "Error closing ndef", e); makeToast("Error closing ndef: " + e, Toast.LENGTH_LONG); } makeToast("Successes: " + SUCCESS_COUNT + ". Failures: " + FAILURE_COUNT); } } private void makeToast(final String msg) { makeToast(msg, Toast.LENGTH_SHORT); } private void makeToast(final String msg, final int duration) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Toast.makeText(NfcTestActivity.this, msg, duration).show(); } }); } }