CardEmulation / src / com.example.android.cardemulation /

CardService.java

1
/*
2
 * Copyright (C) 2013 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.cardemulation;
18
 
19
import android.nfc.cardemulation.HostApduService;
20
import android.os.Bundle;
21
import com.example.android.common.logger.Log;
22
 
23
import java.util.Arrays;
24
 
25
/**
26
 * This is a sample APDU Service which demonstrates how to interface with the card emulation support
27
 * added in Android 4.4, KitKat.
28
 *
29
 * <p>This sample replies to any requests sent with the string "Hello World". In real-world
30
 * situations, you would need to modify this code to implement your desired communication
31
 * protocol.
32
 *
33
 * <p>This sample will be invoked for any terminals selecting AIDs of 0xF11111111, 0xF22222222, or
34
 * 0xF33333333. See src/main/res/xml/aid_list.xml for more details.
35
 *
36
 * <p class="note">Note: This is a low-level interface. Unlike the NdefMessage many developers
37
 * are familiar with for implementing Android Beam in apps, card emulation only provides a
38
 * byte-array based communication channel. It is left to developers to implement higher level
39
 * protocol support as needed.
40
 */
41
public class CardService extends HostApduService {
42
    private static final String TAG = "CardService";
43
    // AID for our loyalty card service.
44
    private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222";
45
    // ISO-DEP command HEADER for selecting an AID.
46
    // Format: [Class | Instruction | Parameter 1 | Parameter 2]
47
    private static final String SELECT_APDU_HEADER = "00A40400";
48
    // "OK" status word sent in response to SELECT AID command (0x9000)
49
    private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000");
50
    // "UNKNOWN" status word sent in response to invalid APDU command (0x0000)
51
    private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000");
52
    private static final byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
53
 
54
    /**
55
     * Called if the connection to the NFC card is lost, in order to let the application know the
56
     * cause for the disconnection (either a lost link, or another AID being selected by the
57
     * reader).
58
     *
59
     * @param reason Either DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED
60
     */
61
    @Override
62
    public void onDeactivated(int reason) { }
63
 
64
    /**
65
     * This method will be called when a command APDU has been received from a remote device. A
66
     * response APDU can be provided directly by returning a byte-array in this method. In general
67
     * response APDUs must be sent as quickly as possible, given the fact that the user is likely
68
     * holding his device over an NFC reader when this method is called.
69
     *
70
     * <p class="note">If there are multiple services that have registered for the same AIDs in
71
     * their meta-data entry, you will only get called if the user has explicitly selected your
72
     * service, either as a default or just for the next tap.
73
     *
74
     * <p class="note">This method is running on the main thread of your application. If you
75
     * cannot return a response APDU immediately, return null and use the {@link
76
     * #sendResponseApdu(byte[])} method later.
77
     *
78
     * @param commandApdu The APDU that received from the remote device
79
     * @param extras A bundle containing extra data. May be null.
80
     * @return a byte-array containing the response APDU, or null if no response APDU can be sent
81
     * at this point.
82
     */
84
    @Override
85
    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
86
        Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu));
87
        // If the APDU matches the SELECT AID command for this service,
88
        // send the loyalty card account number, followed by a SELECT_OK status trailer (0x9000).
89
        if (Arrays.equals(SELECT_APDU, commandApdu)) {
90
            String account = AccountStorage.GetAccount(this);
91
            byte[] accountBytes = account.getBytes();
92
            Log.i(TAG, "Sending account number: " + account);
93
            return ConcatArrays(accountBytes, SELECT_OK_SW);
94
        } else {
95
            return UNKNOWN_CMD_SW;
96
        }
97
    }
99
 
100
    /**
101
     * Build APDU for SELECT AID command. This command indicates which service a reader is
102
     * interested in communicating with. See ISO 7816-4.
103
     *
104
     * @param aid Application ID (AID) to select
105
     * @return APDU for SELECT AID command
106
     */
107
    public static byte[] BuildSelectApdu(String aid) {
108
        // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
109
        return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X",
110
                aid.length() / 2) + aid);
111
    }
112
 
113
    /**
114
     * Utility method to convert a byte array to a hexadecimal string.
115
     *
116
     * @param bytes Bytes to convert
117
     * @return String, containing hexadecimal representation.
118
     */
119
    public static String ByteArrayToHexString(byte[] bytes) {
120
        final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
121
        char[] hexChars = new char[bytes.length * 2]; // Each byte has two hex characters (nibbles)
122
        int v;
123
        for (int j = 0; j < bytes.length; j++) {
124
            v = bytes[j] & 0xFF; // Cast bytes[j] to int, treating as unsigned value
125
            hexChars[j * 2] = hexArray[v >>> 4]; // Select hex character from upper nibble
126
            hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // Select hex character from lower nibble
127
        }
128
        return new String(hexChars);
129
    }
130
 
131
    /**
132
     * Utility method to convert a hexadecimal string to a byte string.
133
     *
134
     * <p>Behavior with input strings containing non-hexadecimal characters is undefined.
135
     *
136
     * @param s String containing hexadecimal characters to convert
137
     * @return Byte array generated from input
138
     * @throws java.lang.IllegalArgumentException if input length is incorrect
139
     */
140
    public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException {
141
        int len = s.length();
142
        if (len % 2 == 1) {
143
            throw new IllegalArgumentException("Hex string must have even number of characters");
144
        }
145
        byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters
146
        for (int i = 0; i < len; i += 2) {
147
            // Convert each character into a integer (base-16), then bit-shift into place
148
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
149
                    + Character.digit(s.charAt(i+1), 16));
150
        }
151
        return data;
152
    }
153
 
154
    /**
155
     * Utility method to concatenate two byte arrays.
156
     * @param first First array
157
     * @param rest Any remaining arrays
158
     * @return Concatenated copy of input arrays
159
     */
160
    public static byte[] ConcatArrays(byte[] first, byte[]... rest) {
161
        int totalLength = first.length;
162
        for (byte[] array : rest) {
163
            totalLength += array.length;
164
        }
165
        byte[] result = Arrays.copyOf(first, totalLength);
166
        int offset = first.length;
167
        for (byte[] array : rest) {
168
            System.arraycopy(array, 0, result, offset, array.length);
169
            offset += array.length;
170
        }
171
        return result;
172
    }
173
}