HostApduService.java revision 583fe9d22b21aa77fad3096bfdad3020d6088230
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.Binder;
8import android.os.Bundle;
9import android.os.Handler;
10import android.os.IBinder;
11import android.os.Message;
12import android.os.Messenger;
13import android.os.Parcel;
14import android.os.RemoteException;
15import android.util.Log;
16
17/**
18 * <p>A convenience class that can be extended to implement
19 * a service that processes ISO7816-4 commands on top of
20 * the ISO14443-4 / IsoDep protocol (T=CL).
21 *
22 * <p>To tell the platform which ISO7816 application ID (AIDs)
23 * are implemented by this service, a {@link #SERVICE_META_DATA}
24 * entry must be included in the declaration of the service. An
25 * example of such a service declaration is shown below:
26 * <pre> &lt;service android:name=".MyHostApduService"&gt;
27 *     &lt;intent-filter&gt;
28 *         &lt;action android:name="android.nfc.HostApduService"/&gt;
29 *     &lt;/intent-filter&gt;
30 *     &lt;meta-data android:name="android.nfc.HostApduService" android:resource="@xml/apduservice.xml"/&gt;
31 * &lt;/service&gt;</pre>
32 * <p>For more details refer to {@link #SERVICE_META_DATA},
33 * <code>&lt;{@link android.R.styleable#HostApduService host-apdu-service}&gt;</code>,
34 * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
35 * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
36 * <p class="note">The Android platform currently only supports a single
37 * logical channel.
38 */
39public abstract class HostApduService extends Service {
40    /**
41     * The {@link Intent} that must be declared as handled by the service.
42     */
43    @SdkConstant(SdkConstantType.SERVICE_ACTION)
44    public static final String SERVICE_INTERFACE =
45            "android.nfc.HostApduService";
46
47    /**
48     * The name of the meta-data element that contains
49     * more information about this service.
50     */
51    public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
52
53    /**
54     * Reason for {@link #onDeactivated(int)}.
55     * Indicates deactivation was due to the NFC link
56     * being lost.
57     */
58    public static final int DEACTIVATION_LINK_LOSS = 0;
59
60    /**
61     * Reason for {@link #onDeactivated(int)}.
62     *
63     * <p>Indicates deactivation was due to a different AID
64     * being selected (which implicitly deselects the AID
65     * currently active on the logical channel).
66     *
67     * <p>Note that this next AID may still be resolved to this
68     * service, in which case {@link #processCommandApdu(byte[], int)}
69     * will be called again.
70     */
71    public static final int DEACTIVATION_DESELECTED = 1;
72
73    static final String TAG = "ApduService";
74
75    /**
76     * MSG_COMMAND_APDU is sent by NfcService when
77     * a 7816-4 command APDU has been received.
78     *
79     * @hide
80     */
81    public static final int MSG_COMMAND_APDU = 0;
82
83    /**
84     * MSG_RESPONSE_APDU is sent to NfcService to send
85     * a response APDU back to the remote device.
86     *
87     * @hide
88     */
89    public static final int MSG_RESPONSE_APDU = 1;
90
91    /**
92     * MSG_DEACTIVATED is sent by NfcService when
93     * the current session is finished; either because
94     * another AID was selected that resolved to
95     * another service, or because the NFC link
96     * was deactivated.
97     *
98     * @hide
99     */
100    public static final int MSG_DEACTIVATED = 2;
101
102    /**
103     *
104     * @hide
105     */
106    public static final int MSG_UNHANDLED = 3;
107
108    /**
109     * @hide
110     */
111    public static final String KEY_DATA = "data";
112
113    /**
114     * Messenger interface to NfcService for sending responses.
115     * Only accessed on main thread by the message handler.
116     *
117     * @hide
118     */
119    Messenger mNfcService = null;
120
121    final Messenger mMessenger = new Messenger(new MsgHandler());
122
123    final class MsgHandler extends Handler {
124        @Override
125        public void handleMessage(Message msg) {
126            switch (msg.what) {
127            case MSG_COMMAND_APDU:
128                Bundle dataBundle = msg.getData();
129                if (dataBundle == null) {
130                    return;
131                }
132                if (mNfcService == null) mNfcService = msg.replyTo;
133
134                byte[] apdu = dataBundle.getByteArray(KEY_DATA);
135                if (apdu != null) {
136                    byte[] responseApdu = processCommandApdu(apdu, 0);
137                    if (responseApdu != null) {
138                        if (mNfcService == null) {
139                            Log.e(TAG, "Response not sent; service was deactivated.");
140                            return;
141                        }
142                        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
143                        Bundle responseBundle = new Bundle();
144                        responseBundle.putByteArray(KEY_DATA, responseApdu);
145                        responseMsg.setData(responseBundle);
146                        responseMsg.replyTo = mMessenger;
147                        try {
148                            mNfcService.send(responseMsg);
149                        } catch (RemoteException e) {
150                            Log.e("TAG", "Response not sent; RemoteException calling into " +
151                                    "NfcService.");
152                        }
153                    }
154                } else {
155                    Log.e(TAG, "Received MSG_COMMAND_APDU without data.");
156                }
157                break;
158            case MSG_RESPONSE_APDU:
159                if (mNfcService == null) {
160                    Log.e(TAG, "Response not sent; service was deactivated.");
161                    return;
162                }
163                try {
164                    msg.replyTo = mMessenger;
165                    mNfcService.send(msg);
166                } catch (RemoteException e) {
167                    Log.e(TAG, "RemoteException calling into NfcService.");
168                }
169                break;
170            case MSG_DEACTIVATED:
171                // Make sure we won't call into NfcService again
172                mNfcService = null;
173                onDeactivated(msg.arg1);
174                break;
175            case MSG_UNHANDLED:
176                if (mNfcService == null) {
177                    Log.e(TAG, "notifyUnhandled not sent; service was deactivated.");
178                    return;
179                }
180                try {
181                    msg.replyTo = mMessenger;
182                    mNfcService.send(msg);
183                } catch (RemoteException e) {
184                    Log.e(TAG, "RemoteException calling into NfcService.");
185                }
186                break;
187            default:
188                super.handleMessage(msg);
189            }
190        }
191    }
192
193    @Override
194    public final IBinder onBind(Intent intent) {
195        return mMessenger.getBinder();
196    }
197
198    /**
199     * Sends a response APDU back to the remote device.
200     *
201     * <p>Note: this method may be called from any thread and will not block.
202     * @param responseApdu A byte-array containing the reponse APDU.
203     */
204    public final void sendResponseApdu(byte[] responseApdu) {
205        Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU);
206        Bundle dataBundle = new Bundle();
207        dataBundle.putByteArray(KEY_DATA, responseApdu);
208        responseMsg.setData(dataBundle);
209        try {
210            mMessenger.send(responseMsg);
211        } catch (RemoteException e) {
212            Log.e("TAG", "Local messenger has died.");
213        }
214    }
215
216    /**
217     * Calling this method allows the service to tell the OS
218     * that it won't be able to complete this transaction -
219     * for example, because it requires data connectivity
220     * that is not present at that moment.
221     *
222     * The OS may use this indication to give the user a list
223     * of alternative applications that can handle the last
224     * AID that was selected. If the user would select an
225     * application from the list, that action by itself
226     * will not cause the default to be changed; the selected
227     * application will be invoked for the next tap only.
228     *
229     * If there are no other applications that can handle
230     * this transaction, the OS will show an error dialog
231     * indicating your service could not complete the
232     * transaction.
233     *
234     * <p>Note: this method may be called anywhere between
235     *    the first {@link #processCommandApdu(byte[], int)}
236     *    call and a {@link #onDeactivated(int)} call.
237     */
238    public final void notifyUnhandled() {
239        Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED);
240        try {
241            mMessenger.send(unhandledMsg);
242        } catch (RemoteException e) {
243            Log.e("TAG", "Local messenger has died.");
244        }
245    }
246
247
248    /**
249     * <p>This method will be called when a command APDU has been received
250     * from a remote device. A response APDU can be provided directly
251     * by returning a byte-array in this method. Note that in general
252     * response APDUs must be sent as quickly as possible, given the fact
253     * that the user is likely holding his device over an NFC reader
254     * when this method is called.
255     *
256     * <p class="note">If there are multiple services that have registered for the same
257     * AIDs in their meta-data entry, you will only get called if the user has
258     * explicitly selected your service, either as a default or just for the next tap.
259     *
260     * <p class="note">This method is running on the main thread of your application.
261     * If you cannot return a response APDU immediately, return null
262     * and use the {@link #sendResponseApdu(byte[])} method later.
263     *
264     * @param commandApdu The APDU that received from the remote device
265     * @param extras A bundle containing extra data. May be null.
266     * @return a byte-array containing the response APDU, or null if no
267     *         response APDU can be sent at this point.
268     */
269    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
270        // TODO make this abstract
271        return processCommandApdu(commandApdu, 0);
272    }
273
274    /**
275     * <p>This method will be called when a command APDU has been received
276     * from a remote device. A response APDU can be provided directly
277     * by returning a byte-array in this method. Note that in general
278     * response APDUs must be sent as quickly as possible, given the fact
279     * that the user is likely holding his device over an NFC reader
280     * when this method is called.
281     *
282     * <p class="note">If there are multiple services that have registered for the same
283     * AIDs in their meta-data entry, you will only get called if the user has
284     * explicitly selected your service, either as a default or just for the next tap.
285     *
286     * <p class="note">This method is running on the main thread of your application.
287     * If you cannot return a response APDU immediately, return null
288     * and use the {@link #sendResponseApdu(byte[])} method later.
289     *
290     * @deprecated use {@link #processCommandApdu(byte[], Bundle)}
291     * @param commandApdu
292     * @param flags
293     * @return a byte-array containing the response APDU, or null if no
294     *         response APDU can be sent at this point.
295     */
296    public abstract byte[] processCommandApdu(byte[] commandApdu, int flags);
297
298    /**
299     * This method will be called in two possible scenarios:
300     * <li>The NFC link has been deactivated or lost
301     * <li>A different AID has been selected and was resolved to a different
302     *     service component
303     * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED}
304     */
305    public abstract void onDeactivated(int reason);
306}
307