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