Android — записать данные nfc в тег с помощью android studio —
Я создаю приложение, в котором мне нужно написать ряд значений, которые приходят ко мне из файла на карту NFC, и я читаю, и я не знаю, с чего начать, у меня есть несколько сомнений.
Прежде всего я понимаю, что идеал — создать класс, который обрабатывает NFC, хотя я полагаю, что это необязательно и может быть сделано в том же классе. Проблема в том, что учебники, которые я вижу, используют только действия и используют метод onNewIntent.
Находясь во фрагменте, я не могу вызвать этот метод, так что это первый шаг, на котором я проигрываю, и я не знаю, нужен ли этот метод, потому что, насколько я понимаю, это запуск приложения, даже если оно закрыто, как если бы оно было читатель, поправь меня, если я ошибаюсь. Я ценю, если вы можете немного помочь мне в том, что я должен делать, потому что после стольких чтений я немного сошел с ума.
Первым делом я бы начал думать о том, как хранить данные.
Данные специально для вашего приложения или вы хотите поделиться ими с другими приложениями?
Будет ли приложение просто записывать данные один раз или будет обновлять его (желая добавить данные к существующим данным, хранящимся на карте?
Обновление: из ваших комментариев о типе данных вам, вероятно, лучше использовать формат NDEF более высокого уровня для хранения ваших данных с использованием пользовательского типа MIME. Это предполагает, что выбранный вами тип карты поддерживает это. Обратите внимание на пример, который я привел — чтение / запись с использованием низкоуровневых команд чтения и записи постранично.
Сколько байтов данных вы хотите сохранить (влияет на технологию карты)
Возможно, вы захотите подумать о том, какую технологию карт NFC вы хотите использовать, возможно, хороший выбор одной из карт серии NTAG 21x.
Какую минимальную версию Android вы хотите настроить?
Я бы не стал использовать метод newIntent, поскольку он очень ненадежен для записи данных, я бы использовал enableReaderMode
, если вы ориентируетесь на достаточно высокую версию Android.
https://developer.android.com/reference/android/nfc/NfcAdapter.html#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle)
Некоторые ответы на вопросы, о которых вам нужно подумать, повлияют на некоторые детали примера.
Обновление: на основе комментариев . Несмотря на то, что вы используете фрагменты, я все равно включил бы механизм обработки NFC в действие.
Причина этого заключается в том, что ОС все еще обрабатывает обнаружение тегов, если вы не «претендуете» на оборудование NFC в каждом фрагменте, то возможно, особенно с форматом данных NDEF, для ОС отображать экран поверх вашего приложения, если пользователь предъявляет карточку в неподходящее время, что дает плохой пользовательский опыт.
В своем мульти-приложении активности я «требую» оборудование NFC в каждом действии, даже если многие из них делают «В теге обнаружен, ничего не делают», потому что они не являются активностью NFC.
Поэтому, если вы не хотите писать один и тот же код в каждом фрагменте, было бы намного лучше вызывать материал NFC из одного вашего действия, а затем в onTagDiscovered
делать что-то вроде (псевдокод): —
Обновлено:
if displaying the NFC user prompt Fragment.
get data to file.
write data to the card.
Notify user that it is done.
else
do nothing when other fragments are displayed.
Или вы могли бы записать карту в любое время, когда приложение открыто (опять же, лучше всего делать это в действии, а не в любом фрагменте)
If card is presented no matter what fragment is being display
get data from the file
write data to the card
Notify user that it is done.
Извините, я не могу сделать пример в Kotlin, но вот скелет Java-примера, извлеченного из моего приложения (не проверено, поэтому могут быть ошибки копирования и вставки)
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
// All normal onCreate Stuff
// Listen to NFC setting changes
this.registerReceiver(mReceiver, filter);
}
// Listen for NFC being turned on while in the App
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) {
final int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
NfcAdapter.STATE_OFF);
switch (state) {
case NfcAdapter.STATE_OFF:
// Tell the user to turn NFC on if App requires it
break;
case NfcAdapter.STATE_TURNING_OFF:
break;
case NfcAdapter.STATE_ON:
enableNfc();
break;
case NfcAdapter.STATE_TURNING_ON:
break;
}
}
}
};
@Override
protected void onResume() {
super.onResume();
enableNfc();
}
@Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
private void enableNfc(){
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter!= null && mNfcAdapter.isEnabled()) {
// Work around some buggy hardware that checks for cards too fast
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1000);
// Listen for all types of card when this App is in the foreground
// Turn platform sounds off as they misdirect users when writing to the card
// Turn of the platform decoding any NDEF data
mNfcAdapter.enableReaderMode(this,
this,
NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
} else {
// Tell the user to turn NFC on if App requires it
}
}
public void onTagDiscovered(Tag tag) {
// This is run in a separate Thread to UI
StringBuilder Uid = new StringBuilder();
boolean successUid = getUID(tag, Uid);
if (!successUid){
// Not a successful read
return;
} else {
// Feedback to user about successful read
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(500);
runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the UI / notify user
}
});
// Finish Task
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public boolean getUID(Tag tag, StringBuilder Uid){
NfcA mNfcA = NfcA.get(tag);
if (mNfcA != null) {
// The tag is NfcA capable
try {
mNfcA.connect();
// Do a Read operation at page 0 an 1
byte[] result = mNfcA.transceive(new byte[] {
(byte)0x3A, // FAST_READ
(byte)(0 & 0x0ff),
(byte)(1 & 0x0ff),
});
if (result == null) {
// either communication to the tag was lost or a NACK was received
// Log and return
return false;
} else if ((result.length == 1) && ((result[0] & 0x00A) != 0x00A)) {
// NACK response according to Digital Protocol/T2TOP
// Log and return
return false;
} else {
// success: response contains ACK or actual data
for (int i = 0; i < result.length; i ) {
// byte 4 is a check byte
if (i == 3) continue;
Uid.append(String.format("X ", result[i]));
}
// Close and return
try {
mNfcA.close();
} catch (IOException e) {
}
return true;
}
} catch (TagLostException e) {
// Log and return
return false;
} catch (IOException e){
// Log and return
return false;
} finally {
try {
mNfcA.close();
} catch (IOException e) {
}
}
} else {
// Log error
return false;
}
}
}
Android nfc read and write example | codexpedia
NFC stands for Near Field Communication, it is a short-range wireless technology that enables the communication between devices over a distance of less than 10 cm. The NFC standard is defined in ISO/IEC 18092. NFC tag is a sticker or some small objects embeded with a chip that can store small amount of data.Depending on how the chip is programmed for the smartphone, it can change various settings, launch apps and perform certain actions just by holding your phone close to it.
The following are the bare minimum code for creating an Android Application for reading from a NFC tag and writing to it. You will need to know the basics of creating an Android application and you need to have NFC enabled device and a NFC tag. To enable NFC on your android device, go to settings -> More -> and enable it. NFC tags costs from $1 to $2.
In manifest.xml, add the following. The uses-permission and uses-feature tags should belong to the manifest tag. The intent-filter and meta-data tags should go into the activity tag of the Main Activity.
<uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
nfc_tech_filter.xml, this file should go into the xml folder in the res, create the xml directory in the res if it is not yet created.
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.Ndef</tech> <!-- class name --> </tech-list> </resources>
activity_main.xml, the layout file for the MainActivity
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Write a message: "> </TextView> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20sp" > <EditText android:id="@ id/edit_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2" android:hint="message" /> <Button android:id="@ id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Write" /> </LinearLayout> <TextView android:id="@ id/nfc_contents" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
MainActivity.java
public class MainActivity extends Activity { public static final String ERROR_DETECTED = "No NFC tag detected!"; public static final String WRITE_SUCCESS = "Text written to the NFC tag successfully!"; public static final String WRITE_ERROR = "Error during writing, is the NFC tag close enough to your device?"; NfcAdapter nfcAdapter; PendingIntent pendingIntent; IntentFilter writeTagFilters[]; boolean writeMode; Tag myTag; Context context; TextView tvNFCContent; TextView message; Button btnWrite; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; tvNFCContent = (TextView) findViewById(R.id.nfc_contents); message = (TextView) findViewById(R.id.edit_message); btnWrite = (Button) findViewById(R.id.button); btnWrite.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if(myTag ==null) { Toast.makeText(context, ERROR_DETECTED, Toast.LENGTH_LONG).show(); } else { write(message.getText().toString(), myTag); Toast.makeText(context, WRITE_SUCCESS, Toast.LENGTH_LONG ).show(); } } catch (IOException e) { Toast.makeText(context, WRITE_ERROR, Toast.LENGTH_LONG ).show(); e.printStackTrace(); } catch (FormatException e) { Toast.makeText(context, WRITE_ERROR, Toast.LENGTH_LONG ).show(); e.printStackTrace(); } } }); nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter == null) { // Stop here, we definitely need NFC Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show(); finish(); } readFromIntent(getIntent()); pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); tagDetected.addCategory(Intent.CATEGORY_DEFAULT); writeTagFilters = new IntentFilter[] { tagDetected }; } /****************************************************************************** **********************************Read From NFC Tag*************************** ******************************************************************************/ private void readFromIntent(Intent intent) { String action = intent.getAction(); if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage[] msgs = null; if (rawMsgs != null) { msgs = new NdefMessage[rawMsgs.length]; for (int i = 0; i < rawMsgs.length; i ) { msgs[i] = (NdefMessage) rawMsgs[i]; } } buildTagViews(msgs); } } private void buildTagViews(NdefMessage[] msgs) { if (msgs == null || msgs.length == 0) return; String text = ""; // String tagId = new String(msgs[0].getRecords()[0].getType()); byte[] payload = msgs[0].getRecords()[0].getPayload(); String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; // Get the Text Encoding int languageCodeLength = payload[0] & 0063; // Get the Language Code, e.g. "en" // String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); try { // Get the Text text = new String(payload, languageCodeLength 1, payload.length - languageCodeLength - 1, textEncoding); } catch (UnsupportedEncodingException e) { Log.e("UnsupportedEncoding", e.toString()); } tvNFCContent.setText("NFC Content: " text); } /****************************************************************************** **********************************Write to NFC Tag**************************** ******************************************************************************/ private void write(String text, Tag tag) throws IOException, FormatException { NdefRecord[] records = { createRecord(text) }; NdefMessage message = new NdefMessage(records); // Get an instance of Ndef for the tag. Ndef ndef = Ndef.get(tag); // Enable I/O ndef.connect(); // Write the message ndef.writeNdefMessage(message); // Close the connection ndef.close(); } private NdefRecord createRecord(String text) throws UnsupportedEncodingException { String lang = "en"; byte[] textBytes = text.getBytes(); byte[] langBytes = lang.getBytes("US-ASCII"); int langLength = langBytes.length; int textLength = textBytes.length; byte[] payload = new byte[1 langLength textLength]; // set status byte (see NDEF spec for actual bits) payload[0] = (byte) langLength; // copy langbytes and textbytes into payload System.arraycopy(langBytes, 0, payload, 1, langLength); System.arraycopy(textBytes, 0, payload, 1 langLength, textLength); NdefRecord recordNFC = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload); return recordNFC; } @Override protected void onNewIntent(Intent intent) { setIntent(intent); readFromIntent(intent); if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())){ myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); } } @Override public void onPause(){ super.onPause(); WriteModeOff(); } @Override public void onResume(){ super.onResume(); WriteModeOn(); } /****************************************************************************** **********************************Enable Write******************************** ******************************************************************************/ private void WriteModeOn(){ writeMode = true; nfcAdapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null); } /****************************************************************************** **********************************Disable Write******************************* ******************************************************************************/ private void WriteModeOff(){ writeMode = false; nfcAdapter.disableForegroundDispatch(this); } }
Complete example in Github
Getter’ы и setter’ы в котлине
Возможно, вы уже заметили, что у нас нет метода
.setText ()
, как в Java. Я хотел бы остановиться здесь, чтобы объяснить, как геттеры и сеттеры работают в Kotlin по сравнению с Java.
Во-первых, вы должны знать, почему мы используем сеттеры и геттеры. Мы используем их, чтобы скрыть переменные класса и разрешить доступ только с помощью методов, чтобы мы могли скрыть элементы класса от клиентов класса и запретить тем же клиентам напрямую изменять наш класс. Предположим, что у нас есть класс Square в Java:
public class Square {
private int a;
Square(){
a = 1;
}
public void setA(int a){
this.a = Math.abs(a);
}
public int getA(){
return this.a;
}
}
Используя метод
setA ()
, мы запрещаем клиентам класса устанавливать отрицательное значение стороне квадрата, оно не должно быть отрицательным. Используя этот подход, мы должны сделать
a
приватным, поэтому его нельзя установить напрямую. Это также означает, что клиент нашего класса не может получить
a
напрямую, поэтому мы должны предоставить getter. Этот getter возвращает
a
. Если у вас есть 10 переменных с аналогичными требованиями, вам необходимо предоставить 10 геттеров. Написание таких строк — это скучная вещь, в которой мы обычно не используем наш разум.
Kotlin облегчает жизнь нашего разработчика. Если вы вызываете
var side: Int = square.a
это не означает, что вы получаете доступ к
a
непосредственно. Это то же самое, что
int side = square.getA();
в Java. Причина заключается в том, что Kotlin автоматически генерирует геттеры и сеттеры по умолчанию. В Котлине, вы должны указать специальный сеттер или геттер, только если он у вас есть. В противном случае, Kotlin автогенерирует его для вас:
var a = 1
set(value) { field = Math.abs(value) }
field
? Что это? Чтобы было ясно, давайте посмотрим на этот код:
var a = 1
set(value) { a = Math.abs(value) }
Это означает, что вы вызываете метод
set
внутри метода
set
, потому что нет прямого доступа к свойству в мире Kotlin. Это создаст бесконечную рекурсию. Когда вы вызываете
a = что-то
, он автоматически вызывает метод set.
Надеюсь, теперь понятно, почему вы должны использовать ключевое слово
field
и как работают сеттеры и геттеры.
Вернемся к нашему коду. Я хотел бы показать вам ещё одну замечательную особенность языка Kotlin, apply:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.apply {
repositoryName.text = "Medium Android Repository Article"
repositoryOwner.text = "Fleka"
numberOfStarts.text = "1000 stars"
}
}
}
apply
позволяет вам вызывать несколько методов на одном экземпляре.
Мы все еще не закончили привязку данных, есть ещё много дел. Давайте создадим класс модели пользовательского интерфейса для репозитория (этот класс модели пользовательского интерфейса для репозитория GitHub хранит данные, которые должны отображаться, не путайте их с паттерном Repository). Чтобы сделать класс Kotlin, вы должны перейти в New -> Kotlin File / Class:
class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)
В Kotlin первичный конструктор является частью заголовка класса. Если вы не хотите предоставлять второй конструктор, это всё! Ваша работа по созданию класса завершена здесь. Нет параметров конструктора для назначений полей, нет геттеров и сеттеров. Целый класс в одной строке!
Вернитесь в класс MainActivity.kt и создайте экземпляр класса Repository:
var repository = Repository("Habrahabr Android Repository Article",
"Fleka", 1000, true)
Как вы можете заметить, для построения объекта не нужно ключевого слова
new
Теперь перейдем к activity_main.xml и добавим тег data:
Мы можем получить доступ к переменной
repository
, которая является типом
Repository
в нашем макете. Например, мы можем сделать следующее в
TextView
с идентификатором
repository_name
android:text="@{repository.repositoryName}"
TextViewrepository_name
будет отображаться текст, полученный из свойства
repositoryName
переменной
repository
. Остается только связать переменную репозитория от xml до
repositoryMainActivity.kt
Нажмите
Build
, чтобы сгенерировать библиотеку привязки данных для создания необходимых классов, вернитесь в
MainActivity
и добавить две строки:
binding.repository = repository
binding.executePendingBindings()
Если вы запустите приложение, вы увидите, что в
TextView
появится «Habrahabr Android Repository Article». Хорошая функция, да? 🙂
Но что произойдёт, если мы сделаем следующее:
Handler().postDelayed({repository.repositoryName="New Name"}, 2000)
Отобразится ли новый текст через 2 секунды? Нет, не отобразится. Вы должны заново установить значение
repository
. Что-то вроде этого будет работать:
Handler().postDelayed({repository.repositoryName="New Name"
binding.repository = repository
binding.executePendingBindings()}, 2000)
Но это скучно, если нужно будет делать это каждый раз, когда мы меняем какое-то свойство. Существует лучшее решение, называемое
Property Observer
Давайте сначала опишем, что такое паттерн
Observer
, нам понадобится это в разделе
rxJava
Возможно, вы уже слышали об androidweekly.net. Это еженедельный информационный бюллетень об Android разработке. Если вы хотите его получить, вам необходимо подписаться на него, указав свой адрес электронной почты. Позже, если вы захотите, вы можете остановить отказаться от подписки на своем сайте.
Это один из примеров паттерна Observer / Observable. В данном случае, Android Weekly — наблюдаемый (Observable), он выпускает информационные бюллетени каждую неделю. Читатели — это наблюдатели (Observers), они подписываются на него, ждут новых выпусков, и, как только они получают её, они читают её, и если некоторые из них решат, что им это не нравится, он / она может прекратить следить.
Property Observer, в нашем случае, представляет собой XML-макет, который будет прослушивать изменения в экземпляре Repository. Таким образом, Repository является наблюдаемым. Например, как только свойство name класса Repository изменяется в экземпляре класса, xml должен обновится без вызова:
binding.repository = repository
binding.executePendingBindings()
Как сделать это с помощью библиотеки привязки данных? Библиотека привязки данных предоставляет нам класс BaseObservable, который должен быть реализован в классе Repository:
class Repository(repositoryName : String, var repositoryOwner: String?, var numberOfStars: Int?
, var hasIssues: Boolean = false) : BaseObservable(){
@get:Bindable
var repositoryName : String = ""
set(value) {
field = value
notifyPropertyChanged(BR.repositoryName)
}
}
BR
— это класс, который автоматически генерируется один раз, когда используется аннотация
Bindable
. Как вы можете видеть, как только новое значение установлено, мы узнаём об этом. Теперь вы можете запустить приложение, и вы увидите, что имя репозитория будет изменено через 2 секунды без повторного вызова функции
executePendingBindings ()
Для этой части это всё. В следующей части я напишу о паттерне MVVM, паттерне Repository и об Android Wrapper Managers. Вы можете найти весь код здесь. Эта статья охватывает код до этого коммита.
How to build a simple smart card emulator & reader for android
Make sure you are checking the `Include Kotlin support` checkbox, and click “Next”
Make sure you are selecting API 19 or higher, as the Card Emulation is only supported starting Android 4.4. Click “Next”, choose “Empty Activity” and “Finish”.
The only reason we are adding an activity is to make things simpler, our Card Emulator will run as a Service all the time in the background, so it doesn’t actually need an activity, but for the sake of simplicity, we will use one.
The first thing we will add is the Manifest permission declaration to use NFC, in your `AndroidManifest.xml` add the following inside the `manifest` tag before the `application tag`:
<uses-permission android:name=”android.permission.NFC” />
Next we will add the requirement for the HCE hardware so that the app only installs on phones that can run the HCE, right under the previous line, add this line:
<uses-feature android:name="android.hardware.nfc.hce"
android:required="true" />
Next, we will declare our HCE service inside the `application` tag:
<service
android:name=".HostCardEmulatorService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter><meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/apduservice" />
</service>
There is a lot going on in this declaration, so let’s go through it:
Let’s create the `apduservice` XML file now: Right-click the `res` folder in your project and choose new → Directory, call it `xml`. Then create a new xml file inside this new directory called `apduservice` and write the following inside:
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/servicedesc"
android:requireDeviceUnlock="false">
<aid-group android:description="@string/aiddescription"
android:category="other">
<aid-filter android:name="A0000002471001"/>
</aid-group>
</host-apdu-service>
The most important part here is the AID filter, which registers our service to be fired if that AID is being selected by a Card Reader.
You have to create the @string values for the descriptions
Now we will create our Service, Right-click your package, select New → Kotlin File/Class
Select `Class` and the same name we put in the Manifest. Click Ok.
Now we will extend `HostApduService` Abstract Class and implement its abstract methods:
class HostCardEmulatorService: HostApduService() {
override fun onDeactivated(reason: Int) {
TODO("not implemented") //To change body of created
// functions use File | Settings | File Templates.
}override fun processCommandApdu(commandApdu: ByteArray?,
extras: Bundle?): ByteArray {
TODO("not implemented") //To change body of created
// functions use File | Settings | File Templates.
}
}
The `onDeactiveted` method will be called when the a different AID has been selected or the NFC connection has been lost.
The `processCommandApdu` method will be called every time a card reader sends an APDU command that is filtered by our manifest filter.
Let’s define a few constants to work with, before the first method, add the following:
companion object {
val TAG = "Host Card Emulator"
val STATUS_SUCCESS = "9000"
val STATUS_FAILED = "6F00"
val CLA_NOT_SUPPORTED = "6E00"
val INS_NOT_SUPPORTED = "6D00"
val AID = "A0000002471001"
val SELECT_INS = "A4"
val DEFAULT_CLA = "00"
val MIN_APDU_LENGTH = 12
}
Just go ahead and write this inside the `onDeactivated`:
Log.d(TAG, "Deactivated: " reason)
Before implementing the `processCommandApdu` method, we will need a couple of helper methods. Create a new Kotlin Class named `Utils` and copy the two following (static in Java) methods:
companion object {
private val HEX_CHARS = "0123456789ABCDEF"
fun hexStringToByteArray(data: String) : ByteArray {val result = ByteArray(data.length / 2)
for (i in 0 until data.length step 2) {
val firstIndex = HEX_CHARS.indexOf(data[i]);
val secondIndex = HEX_CHARS.indexOf(data[i 1]);
val octet = firstIndex.shl(4).or(secondIndex)
result.set(i.shr(1), octet.toByte())
}
return result
}
private val HEX_CHARS_ARRAY = "0123456789ABCDEF".toCharArray()
fun toHex(byteArray: ByteArray) : String {
val result = StringBuffer()
byteArray.forEach {
val octet = it.toInt()
val firstIndex = (octet and 0xF0).ushr(4)
val secondIndex = octet and 0x0F
result.append(HEX_CHARS_ARRAY[firstIndex])
result.append(HEX_CHARS_ARRAY[secondIndex])
}
return result.toString()
}
}
These methods are here to convert between byte arrays and Hexadecimal Strings.
Now let’s write the following into the `processCommandApdu` method:
if (commandApdu == null) {
return Utils.hexStringToByteArray(STATUS_FAILED)
}val hexCommandApdu = Utils.toHex(commandApdu)
if (hexCommandApdu.length < MIN_APDU_LENGTH) {
return Utils.hexStringToByteArray(STATUS_FAILED)
}
if (hexCommandApdu.substring(0, 2) != DEFAULT_CLA) {
return Utils.hexStringToByteArray(CLA_NOT_SUPPORTED)
}
if (hexCommandApdu.substring(2, 4) != SELECT_INS) {
return Utils.hexStringToByteArray(INS_NOT_SUPPORTED)
}
if (hexCommandApdu.substring(10, 24) == AID) {
return Utils.hexStringToByteArray(STATUS_SUCCESS)
} else {
return Utils.hexStringToByteArray(STATUS_FAILED)
}
This is obviously just a mockup to create our first emulation. You can customize this as you like, but what I created here is a simple check for length and CLA and INS that returns a successful APDU (9000) only when we select the predefined AID.
Android Card Reader with NFC Example:
Just like the previous project, create a new project with Android 4.4 as a minimum SDK, and with Kotlin support, with an Empty Activity.
Inside the `AndroidManifest.xml` declare the same NFC permission:
<uses-permission android:name=”android.permission.NFC” />
Also declare the NFC requirement:
<uses-feature android:name="android.hardware.nfc"
android:required="true" />
In your activity_main.xml layout, just add a TextView to see the card responses with
<TextView
android:id="@ id/textView"
....
/>
Copy the `Utils` Class from the previous project because we will use the same methods here as well.
In your Activity, next to the `AppCompatActivity()` extension, add the following:
class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback
This will implement the `ReaderCallback` Interface and you will have to implement the `onTagDiscovered` method.
Inside your Activity, declare the following variable:
private var nfcAdapter: NfcAdapter? = null
In your onCreate method, add the following:
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
This will get the default NfcAdapter for us to use.
Override the `onResume` method as follow:
public override fun onResume() {
super.onResume()
nfcAdapter?.enableReaderMode(this, this,
NfcAdapter.FLAG_READER_NFC_A or
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
null)
}
This will enable reader mode while this activity is running. When dealing with a Smart Card, make sure you search the technology it is using, so you declare it, but mostly you can use NFC_A. The second flag is there so we skip the NDEF interfaces, what that means in our case, is we don’t want Android Beam to be called when on this activity, otherwise, it will interfere with our reader, because Android gives priority to NDEF type before TECH type (or Smart Cards in General)
Don’t forget to override the `onPause()` method and disable the NfcAdapter:
public override fun onPause() {
super.onPause()
nfcAdapter?.disableReaderMode(this)
}
Now the last thing left is to start sending APDU commands once a card is detected, so in `onTagDiscovered` method, let’s write the following:
override fun onTagDiscovered(tag: Tag?) {
val isoDep = IsoDep.get(tag)
isoDep.connect()
val response = isoDep.transceive(Utils.hexStringToByteArray(
"00A4040007A0000002471001"))
runOnUiThread { textView.append("nCard Response: "
Utils.toHex(response)) }
isoDep.close()
}
Now if you run the first app on Phone A, and the second app on Phone B, then put the phones back-to-back, so their NFCs are facing one another, here is what will happen as soon as Phone B detects :
Phone B will send the above APDU command to the HCE of Phone A, which will forward it to our Service and return a 9000 status. You can intentionally change the command to see one of the errors we put in the Service, like using another CLA or INS …
Now here is the good part. If you take Phone B with our Card Reader App, and put your ePassport back (where the Chip is) on the NFC reader of the phone, you will see the 9000 response from the Chip of the Passport.
Wait! What just happened there?
The APDU command we are sending to the Card is the same we saw on the first paragraph. It is trying to Select the AID `A0000002471001`, which is the ID of the Applet that contains the personal data of the holder inside every ICAO compliant ePassport.
Where to go from here:
You can actually use what we went through here to read the personal data inside the passport, but that’s a whole other subject, that I will cover in part two of this series. Basically, we would need to do Basic Access Control (BAC) where we prove to the chip that we know the keys (The keys are generated based on the Number of Document, Expiry Date, and Birthdate). If you are interested in creating a passport reader, you have to read more about BAC and the structure of data inside a Passport, starting Page 106 is where BAC is explained.
By knowing how to emulate a Smart Card, you can write your own mock-up for a Card that you don’t physically have, but have the specs for. Then, you can use the reader we created to read it, and it will work perfectly with the real Card once you have it.
You can also use your phone instead of an actual Card — if you know what the cards contains — by writing an Emulator that acts exactly like the card. This means you don’t have to carry your Card — you could just use the Emulator app to do what you would with the Card.
Download Sources for the Host-based Card Emulator.
Download Sources for the Sample Smart Card Reader.
Written by Mohamed Hamdaoui for TWG: Software makers to the world’s innovators. Browse our career page to learn more about our team.
Processing the message
The intent received will contain an NdefMessage[] array. Since we know the length, it’s easy to process.
privatevoidhandleNfcIntent(IntentNfcIntent){if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())){Parcelable[] receivedArray =NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);if(receivedArray !=null){
messagesReceivedArray.clear();NdefMessage receivedMessage =(NdefMessage) receivedArray[0];NdefRecord[] attachedRecords = receivedMessage.getRecords();for(NdefRecordrecord:attachedRecords){String string =newString(record.getPayload());if(string.equals(getPackageName())){continue;}
messagesReceivedArray.add(string);}Toast.makeText(this,"Received " messagesReceivedArray.size() " Messages",Toast.LENGTH_LONG).show();updateTextViews();}else{Toast.makeText(this,"Received Blank Parcel",Toast.LENGTH_LONG).show();}}}@OverridepublicvoidonNewIntent(Intent intent){handleNfcIntent(intent);}
We are overriding onNewIntent so we can receive and process the message without creating a new activity. It’s not necessary but will help make everything feel fluid. Add a call to handleNfcIntent in the onCreate() and onResume() functions to be sure that all cases are handled.
@OverridepublicvoidonResume(){super.onResume();updateTextViews();handleNfcIntent(getIntent());}
That’s it! You should have a simple functioning NFC messenger. Attaching different types of files is as easy as specifying a different mime type and attaching the binary of the file you want to send. For a full list of supported types and their convenience constructors take a look at the NdefMessage and NdefRecord classes in the Android documentation.