Neste artigo, implementaremos a chamada
emulação de cartão baseada em host (HCE, emulação de cartão bancário no telefone). A rede possui muitas descrições detalhadas dessa tecnologia. Aqui, concentrei-me em trabalhar com aplicativos de emulador e leitor e resolver vários problemas práticos. Sim, você precisará de 2 dispositivos com nfc.
Existem muitos cenários de uso:
sistema de passes , cartões de fidelidade, cartões de transporte, obtenção de informações adicionais sobre exposições no museu,
gerenciador de senhas .
Nesse caso, o aplicativo no telefone que emula o cartão pode ou não ser iniciado e a tela do seu telefone pode estar bloqueada.
Para o Xamarin Android, existem exemplos prontos de
um emulador e
leitor de cartão .
Vamos tentar usar esses exemplos para criar 2 aplicativos Xamarin Forms, um emulador e um leitor, e resolver os seguintes problemas:
- exibir dados do emulador na tela do leitor
- exibe dados do leitor na tela do emulador
- o emulador deve funcionar com um aplicativo não lançado e uma tela bloqueada
- controlar configurações do emulador
- iniciar o aplicativo emulador quando um leitor for detectado
- verificando o status do adaptador nfc e alternando para as configurações nfc
Este artigo é sobre Android, portanto, se você tiver um aplicativo também para iOS, deverá haver uma implementação separada.
O mínimo de teoria.
Conforme escrito na
documentação do
Android , começando com a versão 4.4 (kitkat), foi adicionada a capacidade de emular cartões ISO-DEP e processar comandos APDU.
A emulação de cartão é baseada em serviços Android conhecidos como "serviços HCE".
Quando um usuário conecta um dispositivo a um leitor NFC, o Android precisa entender a qual serviço HCE o leitor deseja se conectar. A ISO / IEC 7816-4 descreve um método de seleção de aplicativos com base no ID do aplicativo (AID).
Se for interessante explorar o belo mundo das matrizes de bytes,
aqui e
aqui são mais detalhados sobre os comandos do APDU. Este artigo usa apenas alguns dos comandos necessários para trocar dados.
Aplicativo Reader
Vamos começar com o leitor, porque é mais simples.
Criamos um novo projeto no Visual Studio como “Mobile App (Xamarin.Forms)”, depois selecionamos o modelo em branco e deixamos apenas a marca de seleção “Android” na seção “Plataformas”.
No projeto android, você precisa fazer o seguinte:
- A classe CardReader - contém várias constantes e o método OnTagDiscovered
- MainActivity - inicialização da classe CardReader, bem como os métodos OnPause e OnResume para ativar / desativar o leitor ao minimizar o aplicativo
- AndroidManifest.xml - permissões para nfc
E no projeto de plataforma cruzada no arquivo App.xaml.cs:
- Método para exibir uma mensagem para o usuário
Classe 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 {
No modo de leitura do adaptador nfc, quando a placa é detectada, o método OnTagDiscovered será chamado. Nele, IsoDep é um objeto com o qual trocaremos comandos com o mapa (isoDep.Transceive (command)). Comandos são matrizes de bytes.
O código mostra que estamos enviando ao emulador uma sequência que consiste no cabeçalho SELECT_APDU_HEADER, no comprimento do nosso AID em bytes e no próprio AID:
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
Aqui você precisa declarar o campo do leitor:
public CardReader cardReader;
e dois métodos auxiliares:
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); }
no método OnCreate (), inicialize o leitor e ative o modo de leitura:
protected override void OnCreate(Bundle savedInstanceState) { ... cardReader = new CardReader(); EnableReaderMode(); LoadApplication(new App()); }
e também, ative / desative o modo de leitura ao minimizar / abrir o aplicativo:
protected override void OnPause() { base.OnPause(); DisableReaderMode(); } protected override void OnResume() { base.OnResume(); EnableReaderMode(); }
App.xaml.cs
Método estático para exibir uma mensagem:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
A
documentação do Android diz que, para usar o nfc em seu aplicativo e funcionar corretamente com ele, é necessário declarar esses elementos no 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" />
Ao mesmo tempo, se seu aplicativo puder usar o nfc, mas essa não for uma função necessária, você poderá pular o elemento usos-recurso e verificar a disponibilidade do nfc durante a operação.
Isso é tudo para o leitor.
Aplicativo emulador
Novamente, crie um novo projeto no Visual Studio como "Mobile App (Xamarin.Forms)", selecione o modelo "Em branco" e deixe apenas a marca de seleção "Android" na seção "Plataformas".
Em um projeto Android, faça o seguinte:
- Classe CardService - aqui você precisa constantes e o método ProcessCommandApdu (), bem como o método SendMessageToActivity ()
- Descrição do serviço em aid_list.xml
- Mecanismo de envio de mensagens no MainActivity
- Lançamento do aplicativo (se necessário)
- AndroidManifest.xml - permissões para nfc
E no projeto de plataforma cruzada no arquivo App.xaml.cs:
- Método para exibir uma mensagem para o usuário
Classe 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 {
Após o recebimento do comando APDU do leitor, o método ProcessCommandApdu será chamado e o comando será transferido para ele como uma matriz de bytes.
Primeiro, verificamos que a mensagem começa com SELECT_APDU_HEADER e, se houver, redigimos uma resposta ao leitor. Na realidade, uma troca pode ocorrer em várias etapas, pergunta-resposta, pergunta-resposta, etc.
Antes da classe, o atributo Serviço descreve os parâmetros do serviço Android. Ao criar, o xamarin converte essa descrição em um elemento desse no 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>
Descrição do serviço em aid_list.xml
Na pasta xml, crie o arquivo 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>
Uma referência a ele está no atributo Serviço na classe CardService - Resource = "@ xml / aid_list"
Aqui, definimos o AUXÍLIO do nosso aplicativo, segundo o qual o leitor o acessará e o atributo requireDeviceUnlock = "false" para que o cartão seja lido com uma tela desbloqueada.
Existem 2 constantes no código:
@string/service_name
e
@string/card_title
. Eles são declarados no arquivo values / strings.xml:
<resources> <string name="card_title">My Loyalty Card</string> <string name="service_name">My Company</string> </resources>
Mecanismo de envio de mensagens:
O serviço não possui links para MainActivity, que no momento do recebimento do comando APDU nem podem ser iniciados. Portanto, enviamos mensagens do CardService para MainActivity usando BroadcastReceiver da seguinte maneira:
Método para enviar uma mensagem do CardService:
private void SendMessageToActivity(string msg) { Intent intent = new Intent("MSG_NAME"); intent.PutExtra("MSG_DATA", msg); SendBroadcast(intent); }
Recebendo uma mensagem:
Crie a classe 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 o MessageReceiver no MainActivity:
protected override void OnCreate(Bundle savedInstanceState) { ... var receiver = new MessageReceiver(); RegisterReceiver(receiver, new IntentFilter("MSG_NAME")); LoadApplication(new App()); }
App.xaml.cs
O mesmo que no método do leitor para exibir uma mensagem:
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
No momento, já temos as seguintes funções:
- exibir dados do emulador na tela do leitor
- exibe dados do leitor na tela do emulador
- O emulador deve funcionar com o aplicativo não em execução e com a tela desligada.
Próximo.
Controle de emulador
Armazenarei as configurações usando o Xamarin.Essentials.
Vamos fazer o seguinte: quando reiniciarmos o aplicativo emulador, atualizaremos a configuração:
Xamarin.Essentials.Preferences.Set("key1", Guid.NewGuid().ToString());
e no método ProcessCommandApdu, pegaremos esse valor novamente toda vez:
var messageToReader = $"Hello Reader! - {Xamarin.Essentials.Preferences.Get("key1", "key1 not found")}";
Agora, toda vez que você reinicia (não minimiza) o aplicativo emulador, vemos um novo guia, por exemplo:
Hello Reader! - 76324a99-b5c3-46bc-8678-5650dab0529d
Além disso, pelas configurações, ligue / desligue o emulador:
Xamarin.Essentials.Preferences.Set("IsEnabled", false);
e no início do método ProcessCommandApdu, adicione:
var IsEnabled = Xamarin.Essentials.Preferences.Get("IsEnabled", false); if (!IsEnabled) return UNKNOWN_CMD_SW;
Esta é uma maneira fácil, mas existem
outras .
Executando o aplicativo emulador quando um leitor é detectado
Se você precisar apenas abrir o aplicativo emulador, adicione a linha no método ProcessCommandApdu:
StartActivity(typeof(MainActivity));
Se você precisar passar parâmetros para o aplicativo, faça o seguinte:
var activity = new Intent(this, typeof(MainActivity)); intent.PutExtra("MSG_DATA", "data for application"); this.StartActivity(activity);
Você pode ler os parâmetros passados na classe MainActivity no método OnCreate:
... LoadApplication(new App()); if (Intent.Extras != null) { var message = Intent.Extras.GetString("MSG_DATA"); await App.DisplayAlertAsync(message); }
Verificando o status do adaptador nfc e alternando para as configurações nfc
Esta seção se aplica ao leitor e ao emulador.
Crie NfcHelper no projeto Android e use DependencyService para acessá-lo no código da 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); } } }
Agora, no projeto de plataforma cruzada, adicione a interface INfcHelper:
namespace ApduServiceCardApp.Services { public interface INfcHelper { NfcAdapterStatus GetNfcAdapterStatus(); void GoToNFCSettings(); } public enum NfcAdapterStatus { Enabled, Disabled, NoAdapter } }
e use tudo isso no 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; } }
Links para o GitHub
emuladorleitor