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