| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.cardemulation; |
| |
| import android.nfc.cardemulation.HostApduService; |
| import android.os.Bundle; |
| import com.example.android.common.logger.Log; |
| |
| import java.util.Arrays; |
| |
| /** |
| * This is a sample APDU Service which demonstrates how to interface with the card emulation support |
| * added in Android 4.4, KitKat. |
| * |
| * <p>This sample replies to any requests sent with the string "Hello World". In real-world |
| * situations, you would need to modify this code to implement your desired communication |
| * protocol. |
| * |
| * <p>This sample will be invoked for any terminals selecting AIDs of 0xF11111111, 0xF22222222, or |
| * 0xF33333333. See src/main/res/xml/aid_list.xml for more details. |
| * |
| * <p class="note">Note: This is a low-level interface. Unlike the NdefMessage many developers |
| * are familiar with for implementing Android Beam in apps, card emulation only provides a |
| * byte-array based communication channel. It is left to developers to implement higher level |
| * protocol support as needed. |
| */ |
| public class CardService extends HostApduService { |
| private static final String TAG = "CardService"; |
| // AID for our loyalty card service. |
| private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222"; |
| // ISO-DEP command HEADER for selecting an AID. |
| // Format: [Class | Instruction | Parameter 1 | Parameter 2] |
| private static final String SELECT_APDU_HEADER = "00A40400"; |
| // "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); |
| |
| /** |
| * 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. |
| */ |
| // BEGIN_INCLUDE(processCommandApdu) |
| @Override |
| public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { |
| Log.i(TAG, "Received APDU: " + ByteArrayToHexString(commandApdu)); |
| // If the APDU matches the SELECT AID command for this service, |
| // send the loyalty card account number, followed by a SELECT_OK status trailer (0x9000). |
| if (Arrays.equals(SELECT_APDU, commandApdu)) { |
| String account = AccountStorage.GetAccount(this); |
| byte[] accountBytes = account.getBytes(); |
| Log.i(TAG, "Sending account number: " + account); |
| return ConcatArrays(accountBytes, SELECT_OK_SW); |
| } else { |
| return UNKNOWN_CMD_SW; |
| } |
| } |
| // END_INCLUDE(processCommandApdu) |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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; |
| } |
| } |