HostApduService.java revision c3f0044abe657d3e6d9cd1f322b419abddeba20c
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     *
102     * @hide
103     */
104    public static final int MSG_UNHANDLED = 3;
105
106    /**
107     * @hide
108     */
109    public static final String KEY_DATA = "data";
110
111    /**
112     * Messenger interface to NfcService for sending responses.
113     * Only accessed on main thread by the message handler.
114     *
115     * @hide
116     */
117    Messenger mNfcService = null;
118
119    final Messenger mMessenger = new Messenger(new MsgHandler());
120
121    final class MsgHandler extends Handler {
122        @Override
123        public void handleMessage(Message msg) {
124            switch (msg.what) {
125            case MSG_COMMAND_APDU:
126                Bundle dataBundle = msg.getData();
127                if (dataBundle == null) {
128                    return;
129                }
130                if (mNfcService == null) mNfcService = msg.replyTo;
131
132                byte[] apdu = dataBundle.getByteArray(KEY_DATA);
133                if (apdu != null) {
134                    byte[] responseApdu = processCommandApdu(apdu, 0);
135                    if (responseApdu != null) {
136                        if (mNfcService == null) {
137                            Log.e(TAG, "Response not sent; service was deactivated.");
138                            return;
139                        }
140                        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
141                        Bundle responseBundle = new Bundle();
142                        responseBundle.putByteArray(KEY_DATA, responseApdu);
143                        responseMsg.setData(responseBundle);
144                        responseMsg.replyTo = mMessenger;
145                        try {
146                            mNfcService.send(responseMsg);
147                        } catch (RemoteException e) {
148                            Log.e("TAG", "Response not sent; RemoteException calling into " +
149                                    "NfcService.");
150                        }
151                    }
152                } else {
153                    Log.e(TAG, "Received MSG_COMMAND_APDU without data.");
154                }
155                break;
156            case MSG_RESPONSE_APDU:
157                if (mNfcService == null) {
158                    Log.e(TAG, "Response not sent; service was deactivated.");
159                    return;
160                }
161                try {
162                    msg.replyTo = mMessenger;
163                    mNfcService.send(msg);
164                } catch (RemoteException e) {
165                    Log.e(TAG, "RemoteException calling into NfcService.");
166                }
167                break;
168            case MSG_DEACTIVATED:
169                // Make sure we won't call into NfcService again
170                mNfcService = null;
171                onDeactivated(msg.arg1);
172                break;
173            case MSG_UNHANDLED:
174                if (mNfcService == null) {
175                    Log.e(TAG, "notifyUnhandled not sent; service was deactivated.");
176                    return;
177                }
178                try {
179                    msg.replyTo = mMessenger;
180                    mNfcService.send(msg);
181                } catch (RemoteException e) {
182                    Log.e(TAG, "RemoteException calling into NfcService.");
183                }
184                break;
185            default:
186                super.handleMessage(msg);
187            }
188        }
189    }
190
191    @Override
192    public final IBinder onBind(Intent intent) {
193        return mMessenger.getBinder();
194    }
195
196    /**
197     * Sends a response APDU back to the remote device.
198     *
199     * <p>Note: this method may be called from any thread and will not block.
200     * @param responseApdu A byte-array containing the reponse APDU.
201     */
202    public final void sendResponseApdu(byte[] responseApdu) {
203        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
204        Bundle dataBundle = new Bundle();
205        dataBundle.putByteArray(KEY_DATA, responseApdu);
206        responseMsg.setData(dataBundle);
207        try {
208            mMessenger.send(responseMsg);
209        } catch (RemoteException e) {
210            Log.e("TAG", "Local messenger has died.");
211        }
212    }
213
214    /**
215     * Calling this method allows the service to tell the OS
216     * that it won't be able to complete this transaction -
217     * for example, because it requires data connectivity
218     * that is not present at that moment.
219     *
220     * The OS may use this indication to give the user a list
221     * of alternative applications that can handle the last
222     * AID that was selected. If the user would select an
223     * application from the list, that action by itself
224     * will not cause the default to be changed; the selected
225     * application will be invoked for the next tap only.
226     *
227     * If there are no other applications that can handle
228     * this transaction, the OS will show an error dialog
229     * indicating your service could not complete the
230     * transaction.
231     *
232     * <p>Note: this method may be called anywhere between
233     *    the first {@link #processCommandApdu(byte[], int)}
234     *    call and a {@link #onDeactivated(int)} call.
235     */
236    public final void notifyUnhandled() {
237        Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED);
238        try {
239            mMessenger.send(unhandledMsg);
240        } catch (RemoteException e) {
241            Log.e("TAG", "Local messenger has died.");
242        }
243    }
244
245
246    /**
247     * <p>This method will be called when a command APDU has been received
248     * from a remote device. A response APDU can be provided directly
249     * by returning a byte-array in this method. Note that in general
250     * response APDUs must be sent as quickly as possible, given the fact
251     * that the user is likely holding his device over an NFC reader
252     * when this method is called.
253     *
254     * <p class="note">If there are multiple services that have registered for the same
255     * AIDs in their meta-data entry, you will only get called if the user has
256     * explicitly selected your service, either as a default or just for the next tap.
257     *
258     * <p class="note">This method is running on the main thread of your application.
259     * If you cannot return a response APDU immediately, return null
260     * and use the {@link #sendResponseApdu(byte[])} method later.
261     *
262     * @param commandApdu The APDU that received from the remote device
263     * @param extras A bundle containing extra data. May be null.
264     * @return a byte-array containing the response APDU, or null if no
265     *         response APDU can be sent at this point.
266     */
267    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
268        // TODO make this abstract
269        return processCommandApdu(commandApdu, 0);
270    }
271
272    /**
273     * <p>This method will be called when a command APDU has been received
274     * from a remote device. A response APDU can be provided directly
275     * by returning a byte-array in this method. Note that in general
276     * response APDUs must be sent as quickly as possible, given the fact
277     * that the user is likely holding his device over an NFC reader
278     * when this method is called.
279     *
280     * <p class="note">If there are multiple services that have registered for the same
281     * AIDs in their meta-data entry, you will only get called if the user has
282     * explicitly selected your service, either as a default or just for the next tap.
283     *
284     * <p class="note">This method is running on the main thread of your application.
285     * If you cannot return a response APDU immediately, return null
286     * and use the {@link #sendResponseApdu(byte[])} method later.
287     *
288     * @deprecated use {@link #processCommandApdu(byte[], Bundle)}
289     * @param commandApdu
290     * @param flags
291     * @return a byte-array containing the response APDU, or null if no
292     *         response APDU can be sent at this point.
293     */
294    public abstract byte[] processCommandApdu(byte[] commandApdu, int flags);
295
296    /**
297     * This method will be called in two possible scenarios:
298     * <li>The NFC link has been deactivated or lost
299     * <li>A different AID has been selected and was resolved to a different
300     *     service component
301     * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED}
302     */
303    public abstract void onDeactivated(int reason);
304}
305