Pada artikel ini kita akan menerapkan
Emulasi Kartu Berbasis Host (HCE, emulasi kartu Bank pada ponsel). Jaringan ini memiliki banyak uraian terperinci tentang teknologi ini, di sini saya fokus pada mendapatkan emulator dan aplikasi pembaca yang berfungsi dan memecahkan sejumlah masalah praktis. Ya, Anda akan membutuhkan 2 perangkat dengan nfc.
Ada banyak skenario penggunaan:
sistem lulus , kartu loyalitas, kartu transportasi, memperoleh informasi tambahan tentang pameran di museum,
pengelola kata sandi .
Dalam hal ini, aplikasi pada ponsel yang meniru kartu mungkin atau tidak dapat diluncurkan dan layar ponsel Anda mungkin terkunci.
Untuk Xamarin Android, ada contoh siap pakai dari
emulator kartu dan
pembaca .
Mari kita coba menggunakan contoh-contoh ini untuk membuat 2 aplikasi Xamarin Forms, sebuah emulator dan pembaca, dan memecahkan masalah-masalah berikut di dalamnya:
- menampilkan data dari emulator pada layar pembaca
- menampilkan data dari pembaca di layar emulator
- emulator harus bekerja dengan aplikasi yang belum dirilis dan layar yang terkunci
- kontrol pengaturan emulator
- luncurkan aplikasi emulator ketika pembaca terdeteksi
- memeriksa status adaptor nfc dan beralih ke pengaturan nfc
Artikel ini tentang android, oleh karena itu, jika Anda memiliki aplikasi juga untuk iOS, maka harus ada implementasi yang terpisah.
Minimal teori.
Seperti yang tertulis dalam
dokumentasi android , dimulai dengan versi 4.4 (kitkat), kemampuan untuk meniru kartu ISO-DEP dan memproses perintah APDU telah ditambahkan.
Emulasi kartu didasarkan pada layanan android yang dikenal sebagai βlayanan HCEβ.
Ketika pengguna memasang perangkat ke pembaca NFC, android perlu memahami layanan HCE mana yang ingin dihubungkan oleh pembaca. ISO / IEC 7816-4 menjelaskan metode pemilihan aplikasi berdasarkan ID Aplikasi (AID).
Jika menarik untuk mempelajari dunia array byte yang indah, maka di
sini dan di
sini lebih rinci tentang perintah APDU. Artikel ini hanya menggunakan beberapa perintah yang diperlukan untuk bertukar data.
Aplikasi pembaca
Mari kita mulai dengan pembaca, karena ini lebih sederhana.
Kami membuat proyek baru di Visual Studio seperti "Aplikasi Seluler (Xamarin.Forms)", lalu pilih templat "Kosong" dan hanya meninggalkan tanda centang "Android" di bagian "Platform".
Dalam proyek android Anda perlu melakukan hal berikut:
- Kelas CardReader - berisi beberapa konstanta dan metode OnTagDiscovered
- MainActivity - inisialisasi kelas CardReader, serta metode OnPause dan OnResume untuk menghidupkan / mematikan pembaca saat meminimalkan aplikasi
- AndroidManifest.xml - izin untuk nfc
Dan dalam proyek lintas platform dalam file App.xaml.cs:
- Metode untuk menampilkan pesan kepada pengguna
Kelas 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 {
Dalam mode baca adaptor nfc, ketika kartu terdeteksi, metode OnTagDiscovered akan dipanggil. Di dalamnya, IsoDep adalah objek yang dengannya kita akan bertukar perintah dengan peta (isoDep.Transceive (perintah)). Perintah adalah byte array.
Kode menunjukkan bahwa kami mengirimkan emulator urutan yang terdiri dari header SELECT_APDU_HEADER, panjang AID kami dalam byte, dan AID itu sendiri:
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
Di sini Anda perlu mendeklarasikan bidang pembaca:
public CardReader cardReader;
dan dua metode pembantu:
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); }
dalam metode OnCreate (), inisialisasi pembaca dan aktifkan mode baca:
protected override void OnCreate(Bundle savedInstanceState) { ... cardReader = new CardReader(); EnableReaderMode(); LoadApplication(new App()); }
dan juga, aktifkan / nonaktifkan mode membaca ketika meminimalkan / membuka aplikasi:
protected override void OnPause() { base.OnPause(); DisableReaderMode(); } protected override void OnResume() { base.OnResume(); EnableReaderMode(); }
App.xaml.cs
Metode statis untuk menampilkan pesan:
public static async Task DisplayAlertAsync(string msg) => await Device.InvokeOnMainThreadAsync(async () => await Current.MainPage.DisplayAlert("message from service", msg, "ok"));
AndroidManifest.xml
Dokumentasi android mengatakan bahwa untuk menggunakan nfc di aplikasi Anda dan bekerja dengan benar, Anda perlu mendeklarasikan elemen-elemen ini di 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" />
Pada saat yang sama, jika aplikasi Anda dapat menggunakan nfc, tetapi ini bukan fungsi yang diperlukan, maka Anda dapat melewati elemen fitur-penggunaan dan memeriksa ketersediaan nfc selama operasi.
Itu saja untuk pembaca.
Aplikasi emulator
Sekali lagi, buat proyek baru di Visual Studio seperti "Aplikasi Seluler (Xamarin.Forms)", lalu pilih templat "Kosong" dan tinggalkan hanya tanda centang "Android" di bagian "Platform".
Dalam proyek Android, lakukan hal berikut:
- Kelas CardService - Anda memerlukan konstanta dan metode ProcessCommandApdu (), serta metode SendMessageToActivity ()
- Deskripsi layanan di aid_list.xml
- Mekanisme pengiriman pesan di MainActivity
- Peluncuran aplikasi (jika perlu)
- AndroidManifest.xml - izin untuk nfc
Dan dalam proyek lintas platform dalam file App.xaml.cs:
- Metode untuk menampilkan pesan kepada pengguna
Kelas 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 {
Setelah menerima perintah APDU dari pembaca, metode ProcessCommandApdu akan dipanggil dan perintah akan ditransfer ke sana sebagai array byte.
Pertama, kami memverifikasi bahwa pesan dimulai dengan SELECT_APDU_HEADER dan, jika demikian, buat tanggapan kepada pembaca. Pada kenyataannya, pertukaran dapat terjadi dalam beberapa langkah, tanya-jawab, tanya-jawab, dll.
Sebelum kelas, atribut Layanan menjelaskan parameter dari layanan android. Saat membangun, xamarin mengubah deskripsi ini menjadi elemen seperti di 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>
Deskripsi layanan di aid_list.xml
Di folder xml, buat file 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>
Referensi untuk itu adalah dalam atribut Layanan di kelas CardService - Resource = "@ xml / aid_list"
Di sini kita mengatur BANTUAN aplikasi kita, yang menurutnya pembaca akan mengaksesnya dan atribut tersebut memerlukanDeviceUnlock = "false" sehingga kartu dibaca dengan layar yang tidak terkunci.
Ada 2 konstanta dalam kode:
@string/service_name
dan
@string/card_title
. Mereka dideklarasikan dalam file values ββ/ strings.xml:
<resources> <string name="card_title">My Loyalty Card</string> <string name="service_name">My Company</string> </resources>
Mekanisme pengiriman pesan:
Layanan tidak memiliki tautan ke MainActivity, yang pada saat menerima perintah APDU bahkan mungkin tidak dimulai. Oleh karena itu, kami mengirim pesan dari CardService ke MainActivity menggunakan BroadcastReceiver sebagai berikut:
Metode untuk mengirim pesan dari CardService:
private void SendMessageToActivity(string msg) { Intent intent = new Intent("MSG_NAME"); intent.PutExtra("MSG_DATA", msg); SendBroadcast(intent); }
Menerima pesan:
Buat kelas 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); } } }
Daftarkan MessageReceiver di MainActivity:
protected override void OnCreate(Bundle savedInstanceState) { ... var receiver = new MessageReceiver(); RegisterReceiver(receiver, new IntentFilter("MSG_NAME")); LoadApplication(new App()); }
App.xaml.cs
Sama seperti pada metode pembaca untuk menampilkan pesan:
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
Saat ini, kami sudah memiliki fungsi-fungsi berikut:
- menampilkan data dari emulator pada layar pembaca
- menampilkan data dari pembaca di layar emulator
- Emulator harus bekerja dengan aplikasi yang tidak berjalan dan layar dimatikan.
Selanjutnya
Kontrol emulator
Saya akan menyimpan pengaturan menggunakan Xamarin.Essentials.
Mari kita lakukan ini: ketika kita me-restart aplikasi emulator, kita akan memperbarui pengaturan:
Xamarin.Essentials.Preferences.Set("key1", Guid.NewGuid().ToString());
dan dalam metode ProcessCommandApdu kami akan mengambil nilai ini lagi setiap kali:
var messageToReader = $"Hello Reader! - {Xamarin.Essentials.Preferences.Get("key1", "key1 not found")}";
Sekarang setiap kali Anda me-restart (tidak meminimalkan) aplikasi emulator, kami melihat panduan baru, misalnya:
Hello Reader! - 76324a99-b5c3-46bc-8678-5650dab0529d
Juga, melalui pengaturan, hidupkan / matikan emulator:
Xamarin.Essentials.Preferences.Set("IsEnabled", false);
dan pada awal metode ProcessCommandApdu tambahkan:
var IsEnabled = Xamarin.Essentials.Preferences.Get("IsEnabled", false); if (!IsEnabled) return UNKNOWN_CMD_SW;
Ini cara yang mudah, tetapi ada yang
lain .
Menjalankan aplikasi emulator ketika pembaca terdeteksi
Jika Anda hanya perlu membuka aplikasi emulator, kemudian tambahkan baris dalam metode ProcessCommandApdu:
StartActivity(typeof(MainActivity));
Jika Anda perlu memberikan parameter ke aplikasi, maka seperti ini:
var activity = new Intent(this, typeof(MainActivity)); intent.PutExtra("MSG_DATA", "data for application"); this.StartActivity(activity);
Anda dapat membaca parameter yang diteruskan di kelas MainActivity dalam metode OnCreate:
... LoadApplication(new App()); if (Intent.Extras != null) { var message = Intent.Extras.GetString("MSG_DATA"); await App.DisplayAlertAsync(message); }
Memeriksa status adaptor nfc dan beralih ke pengaturan nfc
Bagian ini berlaku untuk pembaca dan emulator.
Buat NfcHelper di proyek android dan gunakan DependencyService untuk mengaksesnya dari kode halaman 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); } } }
Sekarang dalam proyek lintas-platform, tambahkan antarmuka INfcHelper:
namespace ApduServiceCardApp.Services { public interface INfcHelper { NfcAdapterStatus GetNfcAdapterStatus(); void GoToNFCSettings(); } public enum NfcAdapterStatus { Enabled, Disabled, NoAdapter } }
dan gunakan semua ini dalam kode 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; } }
Tautan GitHub
emulatorpembaca