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