HostApduService.java revision a7397883de67d674970d91f86d46ccf637e5e543
1package android.nfc.cardemulation;
2
3import android.annotation.SdkConstant;
4import android.annotation.SdkConstant.SdkConstantType;
5import android.app.Service;
6import android.content.Intent;
7import android.os.Bundle;
8import android.os.Handler;
9import android.os.IBinder;
10import android.os.Message;
11import android.os.Messenger;
12import android.os.RemoteException;
13import android.util.Log;
14
15/**
16 * <p>A convenience class that can be extended to implement
17 * a service that processes ISO7816-4 commands on top of
18 * the ISO14443-4 / IsoDep protocol (T=CL).
19 *
20 * <p>To tell the platform which ISO7816 application ID (AIDs)
21 * are implemented by this service, a {@link #SERVICE_META_DATA}
22 * entry must be included in the declaration of the service. An
23 * example of such a service declaration is shown below:
24 * <pre> &lt;service android:name=".MyHostApduService"&gt;
25 *     &lt;intent-filter&gt;
26 *         &lt;action android:name="android.nfc.HostApduService"/&gt;
27 *     &lt;/intent-filter&gt;
28 *     &lt;meta-data android:name="android.nfc.HostApduService" android:resource="@xml/apduservice.xml"/&gt;
29 * &lt;/service&gt;</pre>
30 * <p>For more details refer to {@link #SERVICE_META_DATA},
31 * <code>&lt;{@link android.R.styleable#HostApduService host-apdu-service}&gt;</code>,
32 * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
33 * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
34 * <p class="note">The Android platform currently only supports a single
35 * logical channel.
36 */
37public abstract class HostApduService extends Service {
38    /**
39     * The {@link Intent} that must be declared as handled by the service.
40     */
41    @SdkConstant(SdkConstantType.SERVICE_ACTION)
42    public static final String SERVICE_INTERFACE =
43            "android.nfc.HostApduService";
44
45    /**
46     * The name of the meta-data element that contains
47     * more information about this service.
48     */
49    public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
50
51    /**
52     * Reason for {@link #onDeactivated(int)}.
53     * Indicates deactivation was due to the NFC link
54     * being lost.
55     */
56    public static final int DEACTIVATION_LINK_LOSS = 0;
57
58    /**
59     * Reason for {@link #onDeactivated(int)}.
60     *
61     * <p>Indicates deactivation was due to a different AID
62     * being selected (which implicitly deselects the AID
63     * currently active on the logical channel).
64     *
65     * <p>Note that this next AID may still be resolved to this
66     * service, in which case {@link #processCommandApdu(byte[], int)}
67     * will be called again.
68     */
69    public static final int DEACTIVATION_DESELECTED = 1;
70
71    static final String TAG = "ApduService";
72
73    /**
74     * MSG_COMMAND_APDU is sent by NfcService when
75     * a 7816-4 command APDU has been received.
76     *
77     * @hide
78     */
79    public static final int MSG_COMMAND_APDU = 0;
80
81    /**
82     * MSG_RESPONSE_APDU is sent to NfcService to send
83     * a response APDU back to the remote device.
84     *
85     * @hide
86     */
87    public static final int MSG_RESPONSE_APDU = 1;
88
89    /**
90     * MSG_DEACTIVATED is sent by NfcService when
91     * the current session is finished; either because
92     * another AID was selected that resolved to
93     * another service, or because the NFC link
94     * was deactivated.
95     *
96     * @hide
97     */
98    public static final int MSG_DEACTIVATED = 2;
99
100    /**
101     * @hide
102     */
103    public static final String KEY_DATA = "data";
104
105    /**
106     * Messenger interface to NfcService for sending responses.
107     * Only accessed on main thread by the message handler.
108     */
109    Messenger mNfcService = null;
110
111    final Messenger mMessenger = new Messenger(new MsgHandler());
112
113    final class MsgHandler extends Handler {
114        @Override
115        public void handleMessage(Message msg) {
116            switch (msg.what) {
117            case MSG_COMMAND_APDU:
118                Bundle dataBundle = msg.getData();
119                if (dataBundle == null) {
120                    return;
121                }
122                if (mNfcService == null) mNfcService = msg.replyTo;
123
124                byte[] apdu = dataBundle.getByteArray(KEY_DATA);
125                if (apdu != null) {
126                    byte[] responseApdu = processCommandApdu(apdu, 0);
127                    if (responseApdu != null) {
128                        if (mNfcService == null) {
129                            Log.e(TAG, "Response not sent; service was deactivated.");
130                            return;
131                        }
132                        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
133                        Bundle responseBundle = new Bundle();
134                        responseBundle.putByteArray(KEY_DATA, responseApdu);
135                        responseMsg.setData(responseBundle);
136                        try {
137                            mNfcService.send(responseMsg);
138                        } catch (RemoteException e) {
139                            Log.e("TAG", "Response not sent; RemoteException calling into " +
140                                    "NfcService.");
141                        }
142                    }
143                } else {
144                    Log.e(TAG, "Received MSG_COMMAND_APDU without data.");
145                }
146                break;
147            case MSG_RESPONSE_APDU:
148                if (mNfcService == null) {
149                    Log.e(TAG, "Response not sent; service was deactivated.");
150                    return;
151                }
152                try {
153                    mNfcService.send(msg);
154                } catch (RemoteException e) {
155                    Log.e(TAG, "RemoteException calling into NfcService.");
156                }
157                break;
158            case MSG_DEACTIVATED:
159                // Make sure we won't call into NfcService again
160                mNfcService = null;
161                onDeactivated(msg.arg1);
162                break;
163            default:
164                super.handleMessage(msg);
165            }
166        }
167    }
168
169    @Override
170    public final IBinder onBind(Intent intent) {
171        return mMessenger.getBinder();
172    }
173
174    /**
175     * Sends a response APDU back to the remote device.
176     *
177     * <p>Note: this method may be called from any thread and will not block.
178     * @param responseApdu A byte-array containing the reponse APDU.
179     */
180    public final void sendResponseApdu(byte[] responseApdu) {
181        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
182        Bundle dataBundle = new Bundle();
183        dataBundle.putByteArray(KEY_DATA, responseApdu);
184        responseMsg.setData(dataBundle);
185        try {
186            mMessenger.send(responseMsg);
187        } catch (RemoteException e) {
188            Log.e("TAG", "Local messenger has died.");
189        }
190    }
191
192    /**
193     * <p>This method will be called when a command APDU has been received
194     * from a remote device. A response APDU can be provided directly
195     * by returning a byte-array in this method. Note that in general
196     * response APDUs must be sent as quickly as possible, given the fact
197     * that the user is likely holding his device over an NFC reader
198     * when this method is called.
199     *
200     * <p class="note">If there are multiple services that have registered for the same
201     * AIDs in their meta-data entry, you will only get called if the user has
202     * explicitly selected your service, either as a default or just for the next tap.
203     *
204     * <p class="note">This method is running on the main thread of your application.
205     * If you cannot return a response APDU immediately, return null
206     * and use the {@link #sendResponseApdu(byte[])} method later.
207     *
208     * @param commandApdu
209     * @param flags
210     * @return a byte-array containing the response APDU, or null if no
211     *         response APDU can be sent at this point.
212     */
213    public abstract byte[] processCommandApdu(byte[] commandApdu, int flags);
214
215    /**
216     * This method will be called in two possible scenarios:
217     * <li>The NFC link has been deactivated or lost
218     * <li>A different AID has been selected and was resolved to a different
219     *     service component
220     * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED}
221     */
222    public abstract void onDeactivated(int reason);
223}
224