这是我第一次发布问题,所以请耐心等待。我一直在运行 Android 4.4.2 的三星 Galaxy S4 上开发 NFC 应用程序。手机绝对可以使用基于主机的卡仿真 (HCE),我想用它与处于读/写模式的ACR1252U-A1 NFC 读卡器(高级卡系统)通信。
阅读器连接到 PC,我编写了一个使用 javax.smartcardio 库与阅读器通信的 Java 应用程序。到目前为止,我已经能够从阅读器向手机发送 SELECT 命令,接收手机的响应并在阅读器和手机之间来回发送后续消息。在 Android 端,我从 Android HCE API扩展了HostApduService类。我基本上是在玩硬件,并决定创建一个 Android 应用程序,然后将一些会员卡信息发送到 POS 系统,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
如果我还有什么可以做的,或者我是否遗漏了一些有助于回答我的问题的内容,请告诉我。谢谢!