El otro día quise hacer una prueba de concepto para escribir y leer tags NFC con Android, así que me puse manos a la obra para hacer una aplicación hipersencilla que lea el contenido del tag y luego escriba un valor.
Buscando por la red y en la guía de Android, hay ejemplos de cómo leer y escribir, pero pronto descubrí que no son completos. Así que ya que me ha funcionado la prueba, he decidido compartirlo para quien le pueda resultar útil.
Quizás el elemento que descubrí y que no pude ver en ejemplos por ahí fue el hecho de que una etiqueta virgen no devuelve entre sus tecnologías Ndef, sino NdefFormatable. Si lo piensas, tiene su sentido. El caso es que no se escribe igual usando una y otra tecnología, y una vez que has escrito algo con NdefFormatable ya no te devuelve más esa tecnología, sino Ndef.
El ejemplo utiliza una capacidad de lectura NFC que es la captura en primer plano, es decir, que el Activity que está visible recibe los tags leídos en lugar de que el sistema haga broadcast a los receivers registrados. Para este tipo de aplicación es lo más lógico.
Dejaré el código fuente íntegro a vuestra disposición, y aquí mostraré sólo las partes más significativas.
La aplicación tiene solo dos clases: Un activity que muestra el texto leído y que tiene otro edit adicional en el que si escribimos algo guardará ese texto en el tag después de leerlo. Y una clase auxiliar, llamada NFCIntent que usando el Intent recibido por el Activity cuando se detecta una etiqueta NFC encapsula la lógica de lectura y escritura.
Lo primero es detectar NFC, esto lo hacemos en el onCreate del Activity, y lo almacenamos en una propiedad de la clase:
Esta simple línea nos da acceso al adaptador NFC. En este caso no necesitamos hacer comprobaciones adicionales más allá de ver si es nulo (en cuyo caso el adaptador no estaría presente). Si queremos que la aplicación funcione en versiones anteriores a 2.3, debemos comprobar primero si el sistema tiene esa capacidad para que esta línea no de error.
Además crearemos un PendingIntent que será el que le pasemos al adaptador NFC cuando nos registremos:
this, 0, new Intent( this, getClass() ).addFlags( Intent.FLAG_ACTIVITY_SINGLE_TOP ), 0
);
Importante, el flag FLAG_ACTIVITY_SINGLE_TOP, ya que eso hace que cuando el adaptador NFC lance el Intent sea la instancia actual (la que está arriba en la pila) la que lo reciba, en lugar de crear una nueva instancia de nuestro Activity.
Es importante que en onResume registremos el activity como el “foreground dispatcher” en el adaptador NFC, y de la misma forma que lo desregistremos en el onPause:
{
super.onPause();
if( nfcAdapter != null )
{
nfcAdapter.disableForegroundDispatch( this );
}
}
public void onResume()
{
super.onResume();
if( nfcAdapter != null )
{
if( nfcAdapter.isEnabled() )
{
nfcAdapter.enableForegroundDispatch( this, pendingIntent, null, null );
}
else
{
// ShouldDo: launch system activity to enable NFC or enable NFC directly
runOnUiThread(
new Runnable()
{
public void run() {
Toast.makeText( WriteTagActivity.this, R.string.please_enable_nfc, Toast.LENGTH_LONG ).show();
}
}
);
}
}
else
{
runOnUiThread(
new Runnable()
{
public void run() {
Toast.makeText( WriteTagActivity.this, R.string.no_nfc_error, Toast.LENGTH_LONG ).show();
}
}
);
finish();
}
}
En onResume comprobamos si tenemos adaptador NFC y si está habilitado, para notificar al usuario. Si no hay NFC salimos directamente.
Lo siguiente que hacemos en el activity es recibir el Intent NFC con el siguiente fragmento:
{
// NFC read/write are blocking operations that must be performed on a separate thread
new NfcProcessThread( intent ).start();
}
Como ves, creamos una nueva hebra para procesar el Intent, ya que además de ser buena práctica en general en este caso vamos a realizar operaciones de ES sobre el Tag, y eso son operaciones bloqueantes que no pueden hacerse en la hebra que nos envía el Intent. De no hacer esto, veremos un bonito diálogo diciendo que nuestra aplicación no responde.
La clase interna NfcProcessThread simplemente crea un nuevo NFCIntent, lee el tag y si hemos escrito algo lo escribe en el tag.
Donde de verdad está el “meollo” de la cuestión es en la clase NFCIntent. Esta tiene fundamentalmente dos métodos: read y write.
El método read lee el tag y almacena su contenido para luego poder acceder a el con el getter correspondiente. El método write le pasamos el texto y lo escribe en el tag.
Como es una prueba de concepto estamos escribiendo simplemente texto plano.
En el constructor de la clase recuperamos el Tag y lo guardamos en una propiedad de la clase, y además detectamos si tiene la tecnología Ndef o NdefFormatable:
for( String tech : tagFromIntent.getTechList() )
{
if( tech.equals( Ndef.class.getName() ) )
{
ndef = true;
}
else if( tech.equals( NdefFormatable.class.getName() ) )
{
ndefFormatable = true;
}
}
Para leer el contenido del tag usamos la siguiente técnica:
try
{
ndef.connect();
NdefMessage msg = ndef.getNdefMessage();
NdefRecord[] records = msg.getRecords();
if( records != null )
{
read = new Vector( records.length );
for( final NdefRecord record : records )
{
String text = getText( record.getPayload() );
if( text != null )
{
read.add( text );
}
}
}
else
{
Log.d( "NFCIntent", "Tag is empty" );
}
}
finally
{
try{ ndef.close(); }catch( Exception ignored ){}
}
Lo que hacemos es tomar el mensaje Ndef del tag, y luego los registros que contiene el mensaje Ndef, los vamos leyendo y almacenamos su contenido en un vector de Strings.
Importante: hay que cerrar siempre la conexión al tag. Además de ser la forma adecuada de tratar con cualquier recurso, en este caso no podemos abrir una tecnología sin haber cerrado la anterior.
El método getText es un método interno que interpreta el contenido crudo de getPayload, que es un array de bytes con el String pero con el primer byte que indica la codificación y el idioma, esta es la esencia del método:
Para escribir en el tag usamos la siguiente técnica:
NdefMessage message = new NdefMessage( records );
if( ndef ) // The tag already has Ndef content
{
Ndef ndef = Ndef.get( tagFromIntent );
try
{
ndef.connect();
ndef.writeNdefMessage( message );
}
finally
{
try{ ndef.close(); } catch( Exception ignored ){}
}
}
else if( ndefFormatable ) // No previous Ndef code has been written, but the tag supports it
{
NdefFormatable ndefFormatable = NdefFormatable.get( tagFromIntent );
try
{
ndefFormatable.connect();
ndefFormatable.format( message );
}
finally
{
try{ ndefFormatable.close(); } catch( Exception ignored ){}
}
}
Lo primero creamos un mensaje Ndef sencillo, compuesto por un único registro con un texto plano (el método newTextRecord figura en el manual de Android, y en el código de esta aplicación de ejemplo).
Después, comprobamos si tiene Ndef para escribir el mensaje usando dicha tecnología, y en caso contrario comprobamos si tiene NdefFormatable para usar esa en su lugar (si es un tag virgen).
En ambos casos, notad como siempre cerramos en el bloque finally.
Realmente es sencillo leer y escribir tags NFC con Android (probado en un Google Nexus S).
Referencias:
- “Programming Android“, por Zigurd Mednieks, Laird Dornin, G. Blake Meike, Masumi Nakamura, publicado por O’Reilly
- “Guía del desarrollador de Android” (Google)
Archivos:
Te estoy muy agradecido.
Gracias por compartirlo
Salud