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> <service android:name=".MyHostApduService"> 25 * <intent-filter> 26 * <action android:name="android.nfc.HostApduService"/> 27 * </intent-filter> 28 * <meta-data android:name="android.nfc.HostApduService" android:resource="@xml/apduservice.xml"/> 29 * </service></pre> 30 * <p>For more details refer to {@link #SERVICE_META_DATA}, 31 * <code><{@link android.R.styleable#HostApduService host-apdu-service}></code>, 32 * <code><{@link android.R.styleable#AidGroup aid-group}></code> and 33 * <code><{@link android.R.styleable#AidFilter aid-filter}></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