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