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