NFC Reader и телефон Android
Это первый раз, когда мне приходилось задавать вопросы, поэтому, пожалуйста, будьте терпеливы. Я работал над приложением NFC на Samsung Galaxy S4 под управлением Android 4.4.2. Телефон определенно может использовать эмуляцию карт на основе хоста (HCE), и я хочу использовать это для связи с устройством считывания карт ACR1252U-A1 (Advanced Card Systems), которое находится в режиме считывателя / записи.
Считыватель подключен к ПК, и я написал приложение Java, используя библиотеку javax.smartcardio для связи с читателем. До сих пор я был в состоянии отправить команду SELECT из устройства чтения в телефон, получить ответ от телефона и отправить последующие сообщения назад и вперед между устройством чтения и телефоном. На стороне Android я расширяю класс HostApduService из Android HCE API. Я в основном играю с аппаратными средствами и решил создать приложение для Android, а затем отправлять некоторую информацию о карте лояльности в POS-систему, которая, в свою очередь, отправляет на устройство номер возврата.
Однако связь между устройствами, кажется, работает только тогда, когда телефон заблокирован. Если я разблокирую телефон (домашний экран или что-то еще), мой компьютер попытается установить драйверы для того, что он называет "Смарт-карта", и он не работает (как и ожидалось) или просто не подключается к телефону. По сути, я просто хочу, чтобы приложение Java работало, когда телефон разблокирован и заблокирован.
Вот основной метод моего Java-приложения:
public static void main(String[] args) {
try {
TerminalFactory factory = TerminalFactory.getDefault();
List terminals = factory.terminals().list();
System.out.println("Terminals count: " + terminals.size());
System.out.println("Terminals: " + terminals);
// Get the first terminal in the list
CardTerminal terminal = (CardTerminal) terminals.get(0);
System.out.println("Using terminal: " + terminal);
System.out.println("Waiting for card present...");
terminal.waitForCardPresent(2000);
if (terminal.isCardPresent()) {
System.out.println("Card present!");
}
// Establish a connection with the card using
// "T=0", "T=1", "T=CL" or "*"
Card card = terminal.connect("*");
System.out.println("Card: " + card);
// Get ATR
byte[] baATR = card.getATR().getBytes();
System.out.println("ATR: " + TestSmartCardIO.toString(baATR));
CardChannel channel = card.getBasicChannel();
// Setup terminal device settings (i.e. buzzer and LED)
byte[] data = { (byte) 0xE0, (byte) 0x00, (byte) 0x00, (byte) 0x21,
(byte) 0x01, (byte) 0x77 };
System.out.println("Setting up terminal device...");
card.transmitControlCommand(
IOCTL_SMARTCARD_ACR1251_ACR1252_ESCAPE_COMMAND, data);
/*
* SELECT Command See GlobalPlatform Card Specification (e.g. 2.2,
* section 11.9) CLA: 00 INS: A4 P1: 04 i.e. b3 is set to 1, means
* select by name P2: 00 i.e. first or only occurence Lc: 08 i.e.
* length of AID see below Data: A0 00 00 00 03 00 00 00 AID of the
* card manager
*/
// Create select to select the correct Android application.
System.out.println("Sending SELECT command...");
byte[] selectAidApdu = createSelectAidApdu(AID_ANDROID);
System.out.println("APDU >>: "
+ TestSmartCardIO.toString(selectAidApdu));
ResponseAPDU response = channel.transmit(new CommandAPDU(
selectAidApdu));
System.out.println("APDU <<: "
+ TestSmartCardIO.toString(response.getBytes()));
// Check response to ensure successful.
if (response.getSW() == SW_OK) {
System.out.println("Selection successful.");
String ssNumber = new String(response.getData());
System.out.println("SS Number : " + ssNumber);
// Send another message to device.
System.out.println("Sending Till number.");
byte[] message = { (byte) 0x00, (byte) TILL_ID };
byte[] messageAidApdu = createMessageApdu(message);
System.out.println("APDU >>: "
+ TestSmartCardIO.toString(messageAidApdu));
response = channel.transmit(new CommandAPDU(messageAidApdu));
if (response.getSW() == SW_OK) {
System.out.println("APDU <<: "
+ TestSmartCardIO.toString(response.getBytes()));
String ack = new String(response.getData());
System.out.println("Received : " + ack);
} else {
System.out.println("SW1: " + response.getSW1());
System.out.println("SW2: " + response.getSW2());
}
} else {
System.out.println("SW1: " + response.getSW1());
System.out.println("SW2: " + response.getSW2());
}
// Disconnect
// true: reset the card after disconnecting card.
card.disconnect(true);
} catch (CardException e) {
e.printStackTrace();
}
}
А вот мой сервис на устройстве Android:
import java.util.Arrays;
import android.content.Intent;
import android.content.SharedPreferences;
import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import android.util.Log;
public class MyHostApduService extends HostApduService {
private static final String TAG = "CardService";
// AID for our loyalty card service.
private static final String SAMPLE_LOYALTY_CARD_AID = "F0010203040506";
// ISO-DEP command HEADER for selecting an AID.
// Format: [Class | Instruction | Parameter 1 | Parameter 2]
private static final String SELECT_APDU_HEADER = "00A40400";
private static final String PUT_DATA_APDU_HEADER = "00DA0000";
// "OK" status word sent in response to SELECT AID command (0x9000)
private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
// "UNKNOWN" status word sent in response to invalid APDU command (0x0000)
private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000");
private static final byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
public static final String PREFS_NAME = "MyPrefsFile";
public static final String SS_NUMBER = "ssNumber";
public static final String TILL_NUMBER = "tillNumber";
/**
* Called if the connection to the NFC card is lost, in order to let the
* application know the cause for the disconnection (either a lost link, or
* another AID being selected by the reader).
*
* @param reason
* Either DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED
*/
@Override
public void onDeactivated(int reason) {
}
/**
* This method will be called when a command APDU has been received from a
* remote device. A response APDU can be provided directly by returning a
* byte-array in this method. In general response APDUs must be sent as
* quickly as possible, given the fact that the user is likely holding his
* device over an NFC reader when this method is called.
*
* <p class="note">
* If there are multiple services that have registered for the same AIDs in
* their meta-data entry, you will only get called if the user has
* explicitly selected your service, either as a default or just for the
* next tap.
*
* <p class="note">
* This method is running on the main thread of your application. If you
* cannot return a response APDU immediately, return null and use the
* {@link #sendResponseApdu(byte[])} method later.
*
* @param commandApdu
* The APDU that received from the remote device
* @param extras
* A bundle containing extra data. May be null.
* @return a byte-array containing the response APDU, or null if no response
* APDU can be sent at this point.
*/
@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu));
// Copy command section to determine type of command.
byte[] command = new byte[4];
System.arraycopy(commandApdu, 0, command, 0, 4);
// If the APDU matches the SELECT AID command for this service,
// send the loyalty card account number.
Log.i(TAG, "Command String: " + ByteArrayToHexString(command));
if (Arrays.equals(SELECT_APDU, commandApdu)) {
// Retrieve stored SS number.
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
String account = settings.getString(SS_NUMBER, "00000000");
byte[] accountBytes = account.getBytes();
Log.i(TAG, "Application Selected. Sending account number: "
+ account);
return ConcatArrays(accountBytes, SELECT_OK_SW);
} else if (PUT_DATA_APDU_HEADER.equals(ByteArrayToHexString(command))) {
int dataLength = commandApdu[4];
byte[] data = new byte[dataLength];
System.arraycopy(commandApdu, 5, data, 0, dataLength);
int tillNumber = Integer.parseInt(ByteArrayToHexString(data), 16);
Log.i(TAG, "Till Number: " + tillNumber);
String ack = "ACK";
byte[] ackBytes = ack.getBytes();
Intent i = new Intent();
i.setClass(this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra(TILL_NUMBER, tillNumber);
startActivity(i);
return ConcatArrays(ackBytes, SELECT_OK_SW);
} else {
return UNKNOWN_CMD_SW;
}
}
/**
* Build APDU for SELECT AID command. This command indicates which service a
* reader is interested in communicating with. See ISO 7816-4.
*
* @param aid
* Application ID (AID) to select
* @return APDU for SELECT AID command
*/
public static byte[] BuildSelectApdu(String aid) {
// Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH |
// DATA]
return HexStringToByteArray(SELECT_APDU_HEADER
+ String.format("%02X", aid.length() / 2) + aid);
}
public static byte[] BuildCommandApdu(String command) {
// Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH |
// DATA]
return HexStringToByteArray(command);
}
/**
* Utility method to convert a byte array to a hexadecimal string.
*
* @param bytes
* Bytes to convert
* @return String, containing hexadecimal representation.
*/
public static String ByteArrayToHexString(byte[] bytes) {
final char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] hexChars = new char[bytes.length * 2]; // Each byte has two hex
// characters (nibbles)
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF; // Cast bytes[j] to int, treating as unsigned
// value
hexChars[j * 2] = hexArray[v >>> 4]; // Select hex character from
// upper nibble
hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // Select hex character
// from lower nibble
}
return new String(hexChars);
}
/**
* Utility method to convert a hexadecimal string to a byte string.
*
* <p>
* Behavior with input strings containing non-hexadecimal characters is
* undefined.
*
* @param s
* String containing hexadecimal characters to convert
* @return Byte array generated from input
* @throws java.lang.IllegalArgumentException
* if input length is incorrect
*/
public static byte[] HexStringToByteArray(String s)
throws IllegalArgumentException {
int len = s.length();
if (len % 2 == 1) {
throw new IllegalArgumentException(
"Hex string must have even number of characters");
}
byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters
for (int i = 0; i < len; i += 2) {
// Convert each character into a integer (base-16), then bit-shift
// into place
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* Utility method to concatenate two byte arrays.
*
* @param first
* First array
* @param rest
* Any remaining arrays
* @return Concatenated copy of input arrays
*/
public static byte[] ConcatArrays(byte[] first, byte[]... rest) {
int totalLength = first.length;
for (byte[] array : rest) {
totalLength += array.length;
}
byte[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (byte[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
}
Вы также можете захотеть увидеть мой файл aid.xml, чтобы увидеть мой номер AID:
<?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/SS_title" android:category="other">
<aid-filter android:name="F0010203040506"/>
</aid-group>
</host-apdu-service>
Это выходной журнал для успешного соединения в приложении Java:
Terminals count: 2
Terminals: [PC/SC terminal ACS ACR1252 1S CL Reader PICC 0, PC/SC terminal
ACS ACR12521S CL Reader SAM 0]
Using terminal: PC/SC terminal ACS ACR1252 1S CL Reader PICC 0
Waiting for card present...
Card present!
Card: PC/SC card in ACS ACR1252 1S CL Reader PICC 0, protocol T=1, state OK
ATR: 3B808011
Setting up terminal device...
Sending SELECT command...
APDU >>: 0A4407F0123456
APDU <<: 313132323333343435900
Selection successful.
SS Number : 112233445
Sending Till number.
APDU >>: 0DA00203
APDU <<: 41434B900
Received : ACK
Это выходной журнал для неудачного соединения в приложении Java:
Terminals count: 2
Terminals: [PC/SC terminal ACS ACR1252 1S CL Reader PICC 0, PC/SC terminal ACS
ACR1252 1S CL Reader SAM 0]
Using terminal: PC/SC terminal ACS ACR1252 1S CL Reader PICC 0
Waiting for card present...
Card present!
Card: PC/SC card in ACS ACR1252 1S CL Reader PICC 0, protocol T=1, state OK
ATR: 3B8F801804FCA000361103B000042
Setting up terminal device...
Sending SELECT command...
APDU >>: 0A4407F0123456
APDU <<: 641
SW1: 100
SW2: 1
Пожалуйста, дайте мне знать, если я могу что-то еще сделать или я пропустил что-то, что поможет ответить на мой вопрос. Спасибо!
0 ответов
Это старый пост, но он все еще может быть ссылкой на некоторые. Что-то случилось с командой SELECT, которая удалила нули заполнения.
Команда SELECT отображается как
0A4407F0123456
Хотя должно быть (без пробелов, просто чтобы легко увидеть разницу)
00 A4 04 00 07 F0 01 02 03 04 05 06