En este artículo, implementaremos la denominada
emulación de tarjeta basada en host (HCE, emulación de tarjeta bancaria en el teléfono). La red tiene muchas descripciones detalladas de esta tecnología, aquí me concentré en hacer que las aplicaciones de emulador y lector funcionen y resolver una serie de problemas prácticos. Sí, necesitará 2 dispositivos con nfc.
Hay muchos escenarios de uso:
sistema de pases , tarjetas de fidelización, tarjetas de transporte, obtención de información adicional sobre exhibiciones en el museo,
administrador de contraseñas .
Al mismo tiempo, la aplicación del teléfono que emula la tarjeta puede iniciarse o no, y la pantalla de su teléfono puede estar bloqueada.
Para Xamarin Android, hay ejemplos listos de
un emulador y
lector de tarjetas .
Intentemos usar estos ejemplos para crear 2 aplicaciones de formularios Xamarin, un emulador y un lector, y resolver los siguientes problemas en ellos:
- mostrar datos del emulador en la pantalla del lector
- mostrar datos del lector en la pantalla del emulador
- el emulador debería funcionar con una aplicación que no se ejecuta y una pantalla bloqueada
- controlar la configuración del emulador
- Inicie la aplicación del emulador cuando se detecte un lector
- comprobar el estado del adaptador nfc y cambiar a la configuración nfc
Este artículo trata sobre Android, por lo tanto, si tiene una aplicación también para iOS, entonces debería haber una implementación separada.
El mínimo de la teoría.
Como está escrito en la
documentación de Android , comenzando con la versión 4.4 (kitkat), se ha agregado la capacidad de emular tarjetas ISO-DEP y procesar comandos APDU.
La emulación de tarjeta se basa en servicios de Android conocidos como "servicios HCE".
Cuando un usuario conecta un dispositivo a un lector NFC, el Android necesita comprender a qué servicio HCE quiere conectarse el lector. ISO / IEC 7816-4 describe un método de selección de aplicaciones basado en la ID de aplicación (AID).
Si es interesante profundizar en el hermoso mundo de las matrices de bytes,
aquí y
aquí hay más detalles sobre los comandos APDU. Este artículo utiliza solo un par de comandos necesarios para intercambiar datos.
Aplicación de lector
Comencemos con el lector, porque Es más simple.
Creamos un nuevo proyecto en Visual Studio como "Aplicación móvil (Xamarin.Forms)", luego seleccionamos la plantilla "En blanco" y dejamos solo la marca de verificación "Android" en la sección "Plataformas".
En el proyecto de Android, debe hacer lo siguiente:
- La clase CardReader: contiene varias constantes y el método OnTagDiscovered
- MainActivity: inicialización de la clase CardReader, así como los métodos OnPause y OnResume para encender / apagar el lector al minimizar la aplicación
- AndroidManifest.xml - permisos para nfc
Y en el proyecto multiplataforma en el archivo App.xaml.cs:
- Método para mostrar un mensaje al usuario
Clase CardReader
using Android.Nfc; using Android.Nfc.Tech; using System; using System.Linq; using System.Text; namespace ApduServiceReaderApp.Droid.Services { public class CardReader : Java.Lang.Object, NfcAdapter.IReaderCallback {
En el modo de lectura del adaptador nfc, cuando se detecta la tarjeta, se llamará al método OnTagDiscovered. En él, IsoDep es un objeto con el que intercambiaremos comandos con el mapa (isoDep.Transceive (comando)). Los comandos son conjuntos de bytes.
El código muestra que enviamos al emulador una secuencia que consiste en el encabezado SELECT_APDU_HEADER, la longitud de nuestro AID en bytes y el AID mismo:
0 164 4 0 // SELECT_APDU_HEADER 5 // AID 241 35 69 103 137 // SAMPLE_LOYALTY_CARD_AID (F1 23 45 67 89)
MainActivity Reader
Aquí debe declarar el campo del lector:
public CardReader cardReader;
y dos métodos de ayuda:
private void EnableReaderMode() { var nfc = NfcAdapter.GetDefaultAdapter(this); if (nfc != null) nfc.EnableReaderMode(this, cardReader, READER_FLAGS, null); } private void DisableReaderMode() { var nfc = NfcAdapter.GetDefaultAdapter(this); if (nfc != null) nfc.DisableReaderMode(this); }
en el método OnCreate (), inicialice el lector y habilite el modo de lectura:
protected override void OnCreate(Bundle savedInstanceState) { ... cardReader = new CardReader(); EnableReaderMode(); LoadApplication(new App()); }
y también, habilite / deshabilite el modo de lectura al minimizar / abrir la aplicación:
protected override void OnPause() { base.OnPause(); DisableReaderMode(); } protected override void OnResume() { base.OnResume(); EnableReaderMode(); }
App.xaml.cs
Método estático para mostrar un mensaje:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
La
documentación de Android dice que para usar nfc en su aplicación y trabajar correctamente con ella, debe declarar estos elementos en AndroidManifest.xml:
<uses-permission android:name="android.permission.NFC" /> <uses-sdk android:minSdkVersion="10"/> <uses-sdk android:minSdkVersion="14"/> <uses-feature android:name="android.hardware.nfc" android:required="true" />
Al mismo tiempo, si su aplicación puede usar nfc, pero esta no es una función requerida, puede omitir el elemento de función de usos y verificar la disponibilidad de nfc durante la operación.
Eso es todo para el lector.
Aplicación de emulador
Nuevamente, cree un nuevo proyecto en Visual Studio como "Aplicación móvil (Xamarin.Forms)", luego seleccione la plantilla "En blanco" y deje solo la marca de verificación "Android" en la sección "Plataformas".
En un proyecto de Android, haga lo siguiente:
- Clase CardService: aquí necesita constantes y el método ProcessCommandApdu (), así como el método SendMessageToActivity ()
- Descripción del servicio en aid_list.xml
- Mecanismo de envío de mensajes en MainActivity
- Lanzamiento de la aplicación (si es necesario)
- AndroidManifest.xml - permisos para nfc
Y en el proyecto multiplataforma en el archivo App.xaml.cs:
- Método para mostrar un mensaje al usuario
Clase CardService
using Android.App; using Android.Content; using Android.Nfc.CardEmulators; using Android.OS; using System; using System.Linq; using System.Text; namespace ApduServiceCardApp.Droid.Services { [Service(Exported = true, Enabled = true, Permission = "android.permission.BIND_NFC_SERVICE"), IntentFilter(new[] { "android.nfc.cardemulation.action.HOST_APDU_SERVICE" }, Categories = new[] { "android.intent.category.DEFAULT" }), MetaData("android.nfc.cardemulation.host_apdu_service", Resource = "@xml/aid_list")] public class CardService : HostApduService {
Al recibir el comando APDU del lector, se llamará al método ProcessCommandApdu y el comando se transferirá a él como una matriz de bytes.
Primero, verificamos que el mensaje comienza con SELECT_APDU_HEADER y, si es así, redactamos una respuesta para el lector. En realidad, un intercambio puede tener lugar en varios pasos, pregunta-respuesta pregunta-respuesta, etc.
Antes de la clase, el atributo Servicio describe los parámetros del servicio de Android. Al construir, xamarin convierte esta descripción en un elemento de este tipo en AndroidManifest.xml:
<service name='md51c8b1c564e9c74403ac6103c28fa46ff.CardService' permission='android.permission.BIND_NFC_SERVICE' enabled='true' exported='true'> <meta-data name='android.nfc.cardemulation.host_apdu_service' resource='@res/0x7F100000'> </meta-data> <intent-filter> <action name='android.nfc.cardemulation.action.HOST_APDU_SERVICE'> </action> <category name='android.intent.category.DEFAULT'> </category> </intent-filter> </service>
Descripción del servicio en aid_list.xml
En la carpeta xml, cree el archivo aid_list.xml:
<?xml version="1.0" encoding="utf-8"?> <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/service_name" android:requireDeviceUnlock="false"> <aid-group android:description="@string/card_title" android:category="other"> <aid-filter android:name="F123456789"/> </aid-group> </host-apdu-service>
Una referencia a él está en el atributo Servicio en la clase CardService - Recurso = "@ xml / aid_list"
Aquí establecemos el AID de nuestra aplicación, según el cual el lector accederá a él y el atributo requireDeviceUnlock = "false" para que la tarjeta se lea con una pantalla desbloqueada.
Hay 2 constantes en el código:
@string/service_name
y
@string/card_title
. Se declaran en el archivo values / strings.xml:
<resources> <string name="card_title">My Loyalty Card</string> <string name="service_name">My Company</string> </resources>
Mecanismo de envío de mensajes:
El servicio no tiene enlaces a MainActivity, que al momento de recibir el comando APDU puede que ni siquiera se inicie. Por lo tanto, enviamos mensajes desde CardService a MainActivity usando BroadcastReceiver de la siguiente manera:
Método para enviar un mensaje desde CardService:
private void SendMessageToActivity(string msg) { Intent intent = new Intent("MSG_NAME"); intent.PutExtra("MSG_DATA", msg); SendBroadcast(intent); }
Recibiendo un mensaje:
Cree la clase MessageReceiver:
using Android.Content; namespace ApduServiceCardApp.Droid.Services { public class MessageReceiver : BroadcastReceiver { public override async void OnReceive(Context context, Intent intent) { var message = intent.GetStringExtra("MSG_DATA"); await App.DisplayAlertAsync(message); } } }
Registrar MessageReceiver en MainActivity:
protected override void OnCreate(Bundle savedInstanceState) { ... var receiver = new MessageReceiver(); RegisterReceiver(receiver, new IntentFilter("MSG_NAME")); LoadApplication(new App()); }
App.xaml.cs
Lo mismo que en el método del lector para mostrar un mensaje:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> <uses-feature android:name="FEATURE_NFC_HOST_CARD_EMULATION"/> <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.BIND_NFC_SERVICE" /> <uses-sdk android:minSdkVersion="10"/> 14
Por el momento, ya tenemos las siguientes funciones:
- mostrar datos del emulador en la pantalla del lector
- mostrar datos del lector en la pantalla del emulador
- El emulador debería funcionar con la aplicación que no se ejecuta y con la pantalla apagada.
Siguiente
Control del emulador
Guardaré la configuración usando Xamarin.Essentials.
Hagamos esto: cuando reiniciemos la aplicación del emulador, actualizaremos la configuración:
Xamarin.Essentials.Preferences.Set("key1", Guid.NewGuid().ToString());
y en el método ProcessCommandApdu tomaremos este valor nuevamente cada vez:
var messageToReader = $"Hello Reader! - {Xamarin.Essentials.Preferences.Get("key1", "key1 not found")}";
Ahora, cada vez que reinicia (no minimiza) la aplicación del emulador, vemos una nueva guía, por ejemplo:
Hello Reader! - 76324a99-b5c3-46bc-8678-5650dab0529d
Además, a través de la configuración, enciende / apaga el emulador:
Xamarin.Essentials.Preferences.Set("IsEnabled", false);
y al comienzo del método ProcessCommandApdu agregue:
var IsEnabled = Xamarin.Essentials.Preferences.Get("IsEnabled", false); if (!IsEnabled) return UNKNOWN_CMD_SW;
Esta es una manera fácil, pero hay
otras .
Ejecutar la aplicación del emulador cuando se detecta un lector
Si solo necesita abrir la aplicación del emulador, agregue la línea en el método ProcessCommandApdu:
StartActivity(typeof(MainActivity));
Si necesita pasar parámetros a la aplicación, haga lo siguiente:
var activity = new Intent(this, typeof(MainActivity)); intent.PutExtra("MSG_DATA", "data for application"); this.StartActivity(activity);
Puede leer los parámetros pasados en la clase MainActivity en el método OnCreate:
... LoadApplication(new App()); if (Intent.Extras != null) { var message = Intent.Extras.GetString("MSG_DATA"); await App.DisplayAlertAsync(message); }
Comprobación del estado del adaptador nfc y cambio a la configuración nfc
Esta sección se aplica tanto al lector como al emulador.
Cree NfcHelper en el proyecto de Android y use DependencyService para acceder a él desde el código de la página MainPage.
using Android.App; using Android.Content; using Android.Nfc; using ApduServiceCardApp.Services; using Xamarin.Forms; [assembly: Dependency(typeof(ApduServiceCardApp.Droid.Services.NfcHelper))] namespace ApduServiceCardApp.Droid.Services { public class NfcHelper : INfcHelper { public NfcAdapterStatus GetNfcAdapterStatus() { var adapter = NfcAdapter.GetDefaultAdapter(Forms.Context as Activity); return adapter == null ? NfcAdapterStatus.NoAdapter : adapter.IsEnabled ? NfcAdapterStatus.Enabled : NfcAdapterStatus.Disabled; } public void GoToNFCSettings() { var intent = new Intent(Android.Provider.Settings.ActionNfcSettings); intent.AddFlags(ActivityFlags.NewTask); Android.App.Application.Context.StartActivity(intent); } } }
Ahora en el proyecto multiplataforma, agregue la interfaz INfcHelper:
namespace ApduServiceCardApp.Services { public interface INfcHelper { NfcAdapterStatus GetNfcAdapterStatus(); void GoToNFCSettings(); } public enum NfcAdapterStatus { Enabled, Disabled, NoAdapter } }
y use todo esto en el código MainPage.xaml.cs:
protected override async void OnAppearing() { base.OnAppearing(); await CheckNfc(); } private async Task CheckNfc() { var nfcHelper = DependencyService.Get<INfcHelper>(); var status = nfcHelper.GetNfcAdapterStatus(); switch (status) { case NfcAdapterStatus.Enabled: default: await App.DisplayAlertAsync("nfc enabled!"); break; case NfcAdapterStatus.Disabled: nfcHelper.GoToNFCSettings(); break; case NfcAdapterStatus.NoAdapter: await App.DisplayAlertAsync("no nfc adapter found!"); break; } }
Enlaces GitHub
emuladorlector