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