Home | History | Annotate | Download | only in cardemulation
      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      */
     83     // BEGIN_INCLUDE(processCommandApdu)
     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     }
     98     // END_INCLUDE(processCommandApdu)
     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 }
    174