Считыватель NFC и телефон Android

Это первый раз, когда мне приходилось задавать вопрос, так что проявите терпение. Я работал над приложением NFC на Samsung Galaxy S4 под управлением Android 4.4.2. Телефон определенно может использовать эмуляцию карты на основе хоста (HCE), и я хочу использовать это для связи с ACR1252U-A1 NFC reader (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

Пожалуйста, дайте мне знать, могу ли я еще что-нибудь сделать или пропустил что-то, что поможет ответить на мой вопрос. Спасибо!


person Phil    schedule 14.01.2015    source источник
comment
вы пробовали android: requireDeviceUnlock = true?   -  person Rachita Nanda    schedule 09.02.2015


Ответы (1)


Это старый пост, но он все же может быть ссылкой на некоторые. Что-то произошло с командой SELECT, которая удалила нули заполнения.

Команда ВЫБРАТЬ отображается как

0A4407F0123456

Хотя должно быть (без пробелов, просто чтобы увидеть разницу)

00 A4 04 00 07 F0 01 02 03 04 05 06
person cmmpsantos    schedule 06.12.2019