Intro a NFC Con Android Beam™

O lo que es lo mismo:

Cómo enviar información a través de NFC y no morir en el intento.

Tenemos que afrontarlo, muchas veces la documentación resulta tortuosa. Sí, claro cubre muchos de los escenarios que usaríamos, pero todos muy básicos y con mucha información extra. Por eso, a veces es necesario tener a la mano una guía práctica -a modo de experimento- de cómo usar cierta tecnología. En este caso, hablaremos de NFC.

Para comenzar, he publicado un repo con el código necesario. Este se encuentra en github.

tl;dr

Simplificando a la más mínima expresión, implementar la lectura de un tag NFC en una app se limita a estos pasos:

  1. Definir los permisos / requerimientos en el Manifest
  2. Configurar un IntentFilter para el Activity que escojamos
  3. Indicar qué debe hacer el Activity con la info recibida

Si queremos que la app también pueda enviar mensajes (haciendo uso de Android Beam™), pues podemos agregar un par de pasos más:

  1. Usar la interfaz CreateNdefMessageCallback para enviar un mensaje
  2. Agregar los detalles a enviar

Si bien no se trata de una guía para manejar todos los casos posibles, el objetivo es poder implementar de forma básica el envío de información a través de NFC.

Manos a la obra

1. Definiendo permisos y requerimientos

Agregamos estas estas dos líneas al Manifest (justo dentro de la etiqueta Manifest)

AndroidManifest.xmllink
1
2
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />

Con la primera línea estamos indicando que la app deberá tener acceso al NFC. Con la segunda, ya que este tipo de acciones solo se podrán ejecutar en terminales que tengan NFC, indicamos que la app requiere esta característica (y de paso, nos sirve de filtro para cuando la publiquemos en el Play Store)

2. Configurando el IntentFilter

Seguimos agregando líneas al Manifest. En este caso, debemos hacerlo dentro de la etiqueta correspondiente al Activity que deseamos que reciba la información del Tag NFC.

AndroidManifest.xmllink
1
2
3
4
5
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="@string/mime_type" />
</intent-filter>

Pequeñas aclaraciones: * android.nfc.action.NDEF_DISCOVERED es una acción que identifica el momento en que se ha leído un tag. * android.intent.category.DEFAULT indica que es un Intent común y silvestre. * Por último, el mimeType indica el tipo de dato que estaremos atentos a recibir en la app.
En este caso vemos uno personalizado (que puede ser cualquier cadena inventada, como esta, que fue definida en strings.xml). Para texto plano, simplemente podremos usar text/plain.

¿Por qué usar un mimeType personalizado?

Compatibildad. Más adelante, en la parte de envío de datos, se cubre cómo agregar un Android Application Record (AAR) en el mensaje NFC para poder indicar exactamente con qué app se debe abrir el mensaje. Ya que esta característica fue implementada a partir de la versión 4.0 -que coincide con la publicación de Android Beam™-, versiones anteriores determinan la app a abrir basándose solamente en el mimeType, he ahí la importancia de uno personalizado. Eso no significa tener que crear un tipo de dato nuevo, sino, solamente se trata de darle un nombre que -en muchos casos- solo nuestra app debe conocer.

3. Indicar el comportamiento de la app al leer un tag NFC

Ahora que el proyecto está listo para recibir información a través de NFC, nos falta determinar qué hacer con ella. Como vimos, estos datos serán recibidos a través de un IntentFilter. ¿Qué significa esto? Que tendremos el contenido desde el onCreate de -en este caso- nuestro Activity.

MainActivity.javalink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Identificamos que la acción del intent sea ACTION_NDEF_DISCOVERED
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
    Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

    // Obtenemos los mensajes (sí, pueden ser más de uno)
    if (rawMsgs != null) {
        msgs = new NdefMessage[rawMsgs.length];
        for (int i = 0; i < rawMsgs.length; i++) {
            msgs[i] = (NdefMessage) rawMsgs[i];
        }
    }
}

if (msgs != null) {
    // Por ahora solo nos importan los records del primer mensaje
    NdefRecord[] records = msgs[0].getRecords();

    // Imprimimos los contenidos a la consola
    for(int i = 0; i < records.length; i++) {
        Log.d(LOG_TAG, "Record " + i + " received: " + records[i]);
        Log.d(LOG_TAG, "Record " + i + " received.toMimeType: " + records[i].toMimeType());
        Log.d(LOG_TAG, "Record " + i + " String(getPayload): " +new String(records[i].getPayload()));
    }

    // Finalmente, mostramos lo obtenido en un Snackbar ¡Genial!
    Snackbar.make(findViewById(R.id.toolbar),
            new String(records[0].getPayload()), Snackbar.LENGTH_LONG).show();
}

Dentro del link del source, podrán observar que este código se encuentra dentro del onResume, pero no te preocupes, según la documentación, también está bien hacer la lectura desde ahí.

4. Usando la interfaz CreateNdefMessageCallback

Incluso si tu app no hace uso de esta interfaz, igual podrá enviar su appId a través de NFC. ¿Cómo así? Pues Android Beam™ se encarga de configurar este comportamiento como por defecto para cualquier app.

Sin embargo, si queremos que nuestra app envíe mensajes NFC mejor elaborados, nuestro Activity en cuestión debe implementar la interfaz CreateNdefMessageCallback. Esto nos permitirá definir parámetros personalizados al momento de ejecutar un Beam.

Para ello, simplemente agregamos el siguiente implements:

MainActivity.javalink
1
2
public class MainActivity extends AppCompatActivity
        implements NfcAdapter.CreateNdefMessageCallback{

y el correspondiente método a sobreescribir:

MainActivity.javalink
1
2
3
4
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
    return null;
}

5. Agregar detalles a enviar

Finalmente, dentro del createNdefMessage, definiremos la cantidad y contenido de la información a enviar a través de NFC. En este caso, solo será una cadena que diga "Hola NFC"

MainActivity.javalink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public NdefMessage createNdefMessage(NfcEvent event) {
    String text = ("Hola NFC!");

    NdefMessage msg = new NdefMessage(
        new NdefRecord[] {
            // se recomienda que el primer record sea del mimeType
            // definido para la app, para asegurar mejor compatibilidad
            NdefRecord.createMime(
                    getString(R.string.mime_type), text.getBytes()),

            // Además, para indicar la app específica que debe abrir
            // este mensaje NFC, usamos un AAR
            NdefRecord.createApplicationRecord("com.example.nfcsample")
        });
    return msg;
}

Al hacer uso de un AAR, logramos un efecto interesante: si el dispositivo que recibe el mensaje NFC no tiene nuestra app instalada, será redirigido inmediatamente al Play Store para poder instalarla.

El código correspondiente a los ejemplos de este post se encuentra en github, listo para ser ejecutado en cualquier computadora con el Android SDK instalado.