Leyendo NFC Tags con Android
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
¿Tienes curiosidad por saber qué es NFC y cómo se puede integrar en tus propias aplicaciones de Android? ¡Este tutorial te presentará rápidamente el tema antes de sumergirte y enseñarte cómo construir una aplicación de lector NFC simple!
¿Qué es NFC?
NFC es la abreviatura de Near Field Communication. Es el estándar internacional para el intercambio de datos sin contacto. En contraste con una gran variedad de otras tecnologías, como LAN inalámbrica y Bluetooth, la distancia máxima de dos dispositivos es de 10 cm. El desarrollo de la norma comenzó en 2002 por NXP Semiconductors y Sony. El Foro NFC, un consorcio de más de 170 compañías y miembros, que incluía Mastercard, NXP, Nokia, Samsung, Intel y Google, ha diseñado nuevas especificaciones desde 2004.
Hay varias posibilidades para el uso de NFC con dispositivos móviles; por ejemplo, boletos sin papel, controles de acceso, pagos sin efectivo y llaves de auto. Con la ayuda de las etiquetas NFC puedes controlar tu teléfono y cambiar la configuración. Los datos se pueden intercambiar simplemente sosteniendo dos dispositivos uno al lado del otro.
En este tutorial, quiero explicar cómo implementar NFC con el SDK de Android, qué trampas existen y qué tener en cuenta. Crearemos una aplicación paso a paso, que puede leer el contenido de las etiquetas NFC compatibles con NDEF.
Tecnologías NFC
Hay una variedad de etiquetas NFC que se pueden leer con un teléfono inteligente. El espectro abarca desde simples adhesivos y llaveros hasta tarjetas complejas con hardware criptográfico integrado. Las etiquetas también difieren en su tecnología de chip. El más importante es NDEF, que es compatible con la mayoría de las etiquetas. Además, debe mencionarse a Mifare ya que es la tecnología de chip sin contacto más utilizada en todo el mundo. Algunas etiquetas se pueden leer y escribir, mientras que otras son de solo lectura o cifradas.
Solo el formato de intercambio de datos NFC (NDEF) se describe en este tutorial.



Agregar soporte NFC en una aplicación
Comenzamos con un nuevo proyecto y una actividad en blanco. Es importante seleccionar una versión mínima de SDK de nivel 10, ya que NFC solo es compatible después de Android 2.3.3. Recuerde elegir su propio nombre de paquete. He elegido net.vrallev.android.nfc.demo, porque vrallev.net es el dominio de mi sitio web y la otra parte se refiere al tema de esta aplicación.
1 |
<uses-sdk |
2 |
android:minSdkVersion="10" |
3 |
android:targetSdkVersion="17" /> |
El diseño predeterminado generado por Eclipse es casi suficiente para nosotros. Solo he añadido una ID a TextView y he cambiado el texto.
1 |
<TextView |
2 |
android:id="@+id/textView_explanation" |
3 |
android:layout_width="wrap_content" |
4 |
android:layout_height="wrap_content" |
5 |
android:text="@string/explanation" /> |
Para obtener acceso al hardware NFC, debe solicitar un permiso en el manifiesto. Si la aplicación no funciona sin NFC, puede especificar la condición con la etiqueta de característica de uso. Si se requiere NFC, la aplicación no se puede instalar en dispositivos sin ella y Google Play solo mostrará su aplicación a los usuarios que posean un dispositivo NFC.
1 |
<uses-permission android:name="android.permission.NFC" /> |
2 |
|
3 |
<uses-feature |
4 |
android:name="android.hardware.nfc" |
5 |
android:required="true" /> |
MainActivity solo debe consistir en el método onCreate(). Puede interactuar con el hardware a través de la clase NfcAdapter. Es importante averiguar si el NfcAdapter es nulo. En este caso, el dispositivo Android no es compatible con NFC.
1 |
package net.vrallev.android.nfc.demo; |
2 |
|
3 |
import android.app.Activity; |
4 |
import android.nfc.NfcAdapter; |
5 |
import android.os.Bundle; |
6 |
import android.widget.TextView; |
7 |
import android.widget.Toast; |
8 |
|
9 |
/**
|
10 |
* Activity for reading data from an NDEF Tag.
|
11 |
*
|
12 |
* @author Ralf Wondratschek
|
13 |
*
|
14 |
*/
|
15 |
public class MainActivity extends Activity { |
16 |
|
17 |
public static final String TAG = "NfcDemo"; |
18 |
|
19 |
private TextView mTextView; |
20 |
private NfcAdapter mNfcAdapter; |
21 |
|
22 |
@Override |
23 |
protected void onCreate(Bundle savedInstanceState) { |
24 |
super.onCreate(savedInstanceState); |
25 |
setContentView(R.layout.activity_main); |
26 |
|
27 |
mTextView = (TextView) findViewById(R.id.textView_explanation); |
28 |
|
29 |
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
30 |
|
31 |
if (mNfcAdapter == null) { |
32 |
// Stop here, we definitely need NFC
|
33 |
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show(); |
34 |
finish(); |
35 |
return; |
36 |
|
37 |
}
|
38 |
|
39 |
if (!mNfcAdapter.isEnabled()) { |
40 |
mTextView.setText("NFC is disabled."); |
41 |
} else { |
42 |
mTextView.setText(R.string.explanation); |
43 |
}
|
44 |
|
45 |
handleIntent(getIntent()); |
46 |
}
|
47 |
|
48 |
private void handleIntent(Intent intent) { |
49 |
// TODO: handle Intent
|
50 |
}
|
51 |
}
|
Si iniciamos nuestra aplicación ahora, podemos ver el texto si NFC está habilitado o deshabilitado.
Cómo filtrar las etiquetas NFC
Tenemos nuestra aplicación de muestra y queremos recibir una notificación del sistema cuando adjuntamos una etiqueta NFC al dispositivo. Como es habitual, Android usa su sistema Intent para entregar etiquetas a las aplicaciones. Si varias aplicaciones pueden manejar la Intención, se muestra el selector de actividad y el usuario puede decidir qué aplicación se abrirá. Abrir URLs o compartir información se maneja de la misma manera.
Filtro de Intención NFC
Hay tres filtros diferentes para las etiquetas:
- ACTION_NDEF_DISCOVERED
- ACTION_TECH_DISCOVERED
- ACTION_TAG_DISCOVERED
La lista está ordenada de la prioridad más alta a la más baja.
Ahora, ¿qué sucede cuando se adjunta una etiqueta al teléfono inteligente? Si el sistema detecta una etiqueta con soporte NDEF, se activa un intento. Un intento ACTION_TECH_DISCOVERED se activa si no se registra ninguna Actividad de ninguna aplicación para el Intento NDEF o si la etiqueta no es compatible con NDEF. Si, de nuevo, no se encuentra ninguna aplicación para el Intent o no se pudo detectar la tecnología del chip, se dispara un ACTION_TAG_DISCOVERED Intent. El siguiente gráfico muestra el proceso:



En resumen, esto significa que cada aplicación debe filtrarse después de la Intención con la máxima prioridad. En nuestro caso, esta es la Intención NDEF. Implementamos el intento ACTION_TECH_DISCOVERED primero para resaltar la diferencia entre las prioridades.
La tecnología descubrió la intención
Debemos especificar la tecnología en la que estamos interesados. Para este propósito, creamos una subcarpeta llamada xml en la carpeta res. En esta carpeta creamos el archivo nfc_tech_filter.xml, en el que especificamos las tecnologías.
1 |
<?xml version="1.0" encoding="utf-8"?>
|
2 |
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> |
3 |
<tech-list>
|
4 |
<tech>android.nfc.tech.Ndef</tech> |
5 |
<!-- class name -->
|
6 |
</tech-list>
|
7 |
</resources>
|
8 |
|
9 |
<!--
|
10 |
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
11 |
<tech-list>
|
12 |
<tech>android.nfc.tech.IsoDep</tech>
|
13 |
<tech>android.nfc.tech.NfcA</tech>
|
14 |
<tech>android.nfc.tech.NfcB</tech>
|
15 |
<tech>android.nfc.tech.NfcF</tech>
|
16 |
<tech>android.nfc.tech.NfcV</tech>
|
17 |
<tech>android.nfc.tech.Ndef</tech>
|
18 |
<tech>android.nfc.tech.NdefFormatable</tech>
|
19 |
<tech>android.nfc.tech.MifareClassic</tech>
|
20 |
<tech>android.nfc.tech.MifareUltralight</tech>
|
21 |
</tech-list>
|
22 |
</resources>
|
23 |
-->
|
Ahora debemos crear un IntentFilter en el manifiesto, y la aplicación se iniciará cuando adjuntemos una etiqueta.
1 |
<?xml version="1.0" encoding="utf-8"?>
|
2 |
<activity
|
3 |
android:name="net.vrallev.android.nfc.demo.MainActivity" |
4 |
android:label="@string/app_name" > |
5 |
<intent-filter>
|
6 |
<action android:name="android.intent.action.MAIN" /> |
7 |
<category android:name="android.intent.category.LAUNCHER" /> |
8 |
</intent-filter>
|
9 |
|
10 |
<intent-filter>
|
11 |
<action android:name="android.nfc.action.TECH_DISCOVERED" /> |
12 |
</intent-filter>
|
13 |
|
14 |
<meta-data
|
15 |
android:name="android.nfc.action.TECH_DISCOVERED" |
16 |
android:resource="@xml/nfc_tech_filter" /> |
17 |
</activity>
|
Si no se registra ninguna otra aplicación para esta Intención, nuestra Actividad comenzará de inmediato. Sin embargo, en mi dispositivo hay otras aplicaciones instaladas, por lo que se muestra el selector de actividad.



NDEF Intención descubierta
Como mencioné antes, el Tech Discovered Intent tiene la segunda prioridad más alta. Sin embargo, dado que nuestra aplicación solo es compatible con NDEF, podemos utilizar la Intención Descubierta de NDEF, que tiene una prioridad más alta. Podemos eliminar de nuevo la lista de tecnologías y reemplazar el IntentFilter con el siguiente.
1 |
<intent-filter> |
2 |
<action android:name="android.nfc.action.NDEF_DISCOVERED" /> |
3 |
|
4 |
<category android:name="android.intent.category.DEFAULT" /> |
5 |
|
6 |
<data android:mimeType="text/plain" /> |
7 |
</intent-filter> |
Cuando adjuntemos la etiqueta ahora, la aplicación se iniciará como antes. Sin embargo, hay una diferencia para mí. El selector de actividad no aparece y la aplicación se inicia de inmediato, porque el Intento NDEF tiene una prioridad más alta y las otras aplicaciones solo se registran para las prioridades más bajas. Eso es exactamente lo que queremos.
Despacho de primer plano
Tenga en cuenta que un problema permanece. Cuando nuestra aplicación ya está abierta y adjuntamos la etiqueta nuevamente, la aplicación se abre por segunda vez en lugar de entregar la etiqueta directamente. Este no es nuestro comportamiento previsto. Puede evitar el problema utilizando un Despacho de primer plano.
En lugar de que el sistema haya distribuido la Intención, puede registrar su Actividad para recibir la etiqueta directamente. Esto es importante para un flujo de trabajo en particular, donde no tiene sentido abrir otra aplicación.
He insertado las explicaciones en los lugares apropiados en el código.
1 |
package net.vrallev.android.nfc.demo; |
2 |
|
3 |
import android.app.Activity; |
4 |
import android.app.PendingIntent; |
5 |
import android.content.Intent; |
6 |
import android.content.IntentFilter; |
7 |
import android.content.IntentFilter.MalformedMimeTypeException; |
8 |
import android.nfc.NfcAdapter; |
9 |
import android.os.Bundle; |
10 |
import android.widget.TextView; |
11 |
import android.widget.Toast; |
12 |
|
13 |
/**
|
14 |
* Activity for reading data from an NDEF Tag.
|
15 |
*
|
16 |
* @author Ralf Wondratschek
|
17 |
*
|
18 |
*/
|
19 |
public class MainActivity extends Activity { |
20 |
|
21 |
public static final String MIME_TEXT_PLAIN = "text/plain"; |
22 |
public static final String TAG = "NfcDemo"; |
23 |
|
24 |
private TextView mTextView; |
25 |
private NfcAdapter mNfcAdapter; |
26 |
|
27 |
@Override |
28 |
protected void onCreate(Bundle savedInstanceState) { |
29 |
super.onCreate(savedInstanceState); |
30 |
setContentView(R.layout.activity_main); |
31 |
|
32 |
mTextView = (TextView) findViewById(R.id.textView_explanation); |
33 |
|
34 |
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
35 |
|
36 |
if (mNfcAdapter == null) { |
37 |
// Stop here, we definitely need NFC
|
38 |
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show(); |
39 |
finish(); |
40 |
return; |
41 |
|
42 |
}
|
43 |
|
44 |
if (!mNfcAdapter.isEnabled()) { |
45 |
mTextView.setText("NFC is disabled."); |
46 |
} else { |
47 |
mTextView.setText(R.string.explanation); |
48 |
}
|
49 |
|
50 |
handleIntent(getIntent()); |
51 |
}
|
52 |
|
53 |
@Override |
54 |
protected void onResume() { |
55 |
super.onResume(); |
56 |
|
57 |
/**
|
58 |
* It's important, that the activity is in the foreground (resumed). Otherwise
|
59 |
* an IllegalStateException is thrown.
|
60 |
*/
|
61 |
setupForegroundDispatch(this, mNfcAdapter); |
62 |
}
|
63 |
|
64 |
@Override |
65 |
protected void onPause() { |
66 |
/**
|
67 |
* Call this before onPause, otherwise an IllegalArgumentException is thrown as well.
|
68 |
*/
|
69 |
stopForegroundDispatch(this, mNfcAdapter); |
70 |
|
71 |
super.onPause(); |
72 |
}
|
73 |
|
74 |
@Override |
75 |
protected void onNewIntent(Intent intent) { |
76 |
/**
|
77 |
* This method gets called, when a new Intent gets associated with the current activity instance.
|
78 |
* Instead of creating a new activity, onNewIntent will be called. For more information have a look
|
79 |
* at the documentation.
|
80 |
*
|
81 |
* In our case this method gets called, when the user attaches a Tag to the device.
|
82 |
*/
|
83 |
handleIntent(intent); |
84 |
}
|
85 |
|
86 |
private void handleIntent(Intent intent) { |
87 |
// TODO: handle Intent
|
88 |
}
|
89 |
|
90 |
/**
|
91 |
* @param activity The corresponding {@link Activity} requesting the foreground dispatch.
|
92 |
* @param adapter The {@link NfcAdapter} used for the foreground dispatch.
|
93 |
*/
|
94 |
public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) { |
95 |
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass()); |
96 |
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); |
97 |
|
98 |
final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0); |
99 |
|
100 |
IntentFilter[] filters = new IntentFilter[1]; |
101 |
String[][] techList = new String[][]{}; |
102 |
|
103 |
// Notice that this is the same filter as in our manifest.
|
104 |
filters[0] = new IntentFilter(); |
105 |
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED); |
106 |
filters[0].addCategory(Intent.CATEGORY_DEFAULT); |
107 |
try { |
108 |
filters[0].addDataType(MIME_TEXT_PLAIN); |
109 |
} catch (MalformedMimeTypeException e) { |
110 |
throw new RuntimeException("Check your mime type."); |
111 |
}
|
112 |
|
113 |
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList); |
114 |
}
|
115 |
|
116 |
/**
|
117 |
* @param activity The corresponding {@link BaseActivity} requesting to stop the foreground dispatch.
|
118 |
* @param adapter The {@link NfcAdapter} used for the foreground dispatch.
|
119 |
*/
|
120 |
public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) { |
121 |
adapter.disableForegroundDispatch(activity); |
122 |
}
|
123 |
}
|
Ahora, cuando adjunta una etiqueta y nuestra aplicación ya está abierta, se llama onNewIntent y no se crea una nueva Actividad.
Lectura de datos de una etiqueta NDEF
El último paso es leer los datos de la etiqueta. Las explicaciones se insertan en los lugares apropiados en el código una vez más. La NdefReaderTask es una clase interna privada.
1 |
package net.vrallev.android.nfc.demo; |
2 |
|
3 |
import java.io.UnsupportedEncodingException; |
4 |
import java.util.Arrays; |
5 |
|
6 |
import android.app.Activity; |
7 |
import android.app.PendingIntent; |
8 |
import android.content.Intent; |
9 |
import android.content.IntentFilter; |
10 |
import android.content.IntentFilter.MalformedMimeTypeException; |
11 |
import android.nfc.NdefMessage; |
12 |
import android.nfc.NdefRecord; |
13 |
import android.nfc.NfcAdapter; |
14 |
import android.nfc.Tag; |
15 |
import android.nfc.tech.Ndef; |
16 |
import android.os.AsyncTask; |
17 |
import android.os.Bundle; |
18 |
import android.util.Log; |
19 |
import android.widget.TextView; |
20 |
import android.widget.Toast; |
21 |
|
22 |
/*
|
23 |
* ... other code parts
|
24 |
*/
|
25 |
|
26 |
private void handleIntent(Intent intent) { |
27 |
String action = intent.getAction(); |
28 |
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { |
29 |
|
30 |
String type = intent.getType(); |
31 |
if (MIME_TEXT_PLAIN.equals(type)) { |
32 |
|
33 |
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
34 |
new NdefReaderTask().execute(tag); |
35 |
|
36 |
} else { |
37 |
Log.d(TAG, "Wrong mime type: " + type); |
38 |
}
|
39 |
} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) { |
40 |
|
41 |
// In case we would still use the Tech Discovered Intent
|
42 |
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
43 |
String[] techList = tag.getTechList(); |
44 |
String searchedTech = Ndef.class.getName(); |
45 |
|
46 |
for (String tech : techList) { |
47 |
if (searchedTech.equals(tech)) { |
48 |
new NdefReaderTask().execute(tag); |
49 |
break; |
50 |
}
|
51 |
}
|
52 |
}
|
53 |
}
|
1 |
/**
|
2 |
* Background task for reading the data. Do not block the UI thread while reading.
|
3 |
*
|
4 |
* @author Ralf Wondratschek
|
5 |
*
|
6 |
*/
|
7 |
private class NdefReaderTask extends AsyncTask<Tag, Void, String> { |
8 |
|
9 |
@Override |
10 |
protected String doInBackground(Tag... params) { |
11 |
Tag tag = params[0]; |
12 |
|
13 |
Ndef ndef = Ndef.get(tag); |
14 |
if (ndef == null) { |
15 |
// NDEF is not supported by this Tag.
|
16 |
return null; |
17 |
}
|
18 |
|
19 |
NdefMessage ndefMessage = ndef.getCachedNdefMessage(); |
20 |
|
21 |
NdefRecord[] records = ndefMessage.getRecords(); |
22 |
for (NdefRecord ndefRecord : records) { |
23 |
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) { |
24 |
try { |
25 |
return readText(ndefRecord); |
26 |
} catch (UnsupportedEncodingException e) { |
27 |
Log.e(TAG, "Unsupported Encoding", e); |
28 |
}
|
29 |
}
|
30 |
}
|
31 |
|
32 |
return null; |
33 |
}
|
34 |
|
35 |
private String readText(NdefRecord record) throws UnsupportedEncodingException { |
36 |
/*
|
37 |
* See NFC forum specification for "Text Record Type Definition" at 3.2.1
|
38 |
*
|
39 |
* http://www.nfc-forum.org/specs/
|
40 |
*
|
41 |
* bit_7 defines encoding
|
42 |
* bit_6 reserved for future use, must be 0
|
43 |
* bit_5..0 length of IANA language code
|
44 |
*/
|
45 |
|
46 |
byte[] payload = record.getPayload(); |
47 |
|
48 |
// Get the Text Encoding
|
49 |
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; |
50 |
|
51 |
// Get the Language Code
|
52 |
int languageCodeLength = payload[0] & 0063; |
53 |
|
54 |
// String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
|
55 |
// e.g. "en"
|
56 |
|
57 |
// Get the Text
|
58 |
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); |
59 |
}
|
60 |
|
61 |
@Override |
62 |
protected void onPostExecute(String result) { |
63 |
if (result != null) { |
64 |
mTextView.setText("Read content: " + result); |
65 |
}
|
66 |
}
|
67 |
}
|
La aplicación ahora lee con éxito el contenido.



Aplicaciones útiles
Para verificar si los datos se leen y escriben correctamente, personalmente me gusta usar las siguientes aplicaciones:
- NFC TagInfo de NFC Research Lab para leer datos
- TagInfo by NXP SEMICONDUCTORS para leer datos
- TagWriter por NXP SEMICONDUCTORS para escribir datos
Conclusión
En este tutorial, he mostrado cómo se pueden extraer los datos de una etiqueta NDEF. Podría ampliar el ejemplo a otros tipos de mime y tecnologías de chip; una característica para escribir datos también sería útil. Se realizó el primer paso para trabajar con NFC. Sin embargo, el SDK de Android ofrece muchas más posibilidades, como un fácil intercambio de datos (llamado Android Beam).
Sobre el Autor
Ralf Wondratschek es un estudiante de ciencias informáticas de Alemania. Además de sus estudios, Ralf trabaja como freelance en el campo de la computación móvil. En los últimos años ha trabajado con Java, XML, HTML, JSP, JSF, Eclipse, Google App Engine y, por supuesto, Android. Ha publicado dos aplicaciones de Android hasta la fecha que se pueden encontrar aquí.
Puede encontrar más información sobre el trabajo del autor en su página de inicio vrallev.net.
Fuentes
http://www.nfc-forum.org/home/n-mark.jpg
http://commons.wikimedia.org/wiki/File%3A%C3%9Cberlagert.jpg
http://developer.android.com/images/nfc_tag_dispatch.png



