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> <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.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