1 package android.nfc.cardemulation; 2 3 import android.annotation.SdkConstant; 4 import android.annotation.SdkConstant.SdkConstantType; 5 import android.app.Service; 6 import android.content.Intent; 7 import android.content.pm.PackageManager; 8 import android.os.Bundle; 9 import android.os.Handler; 10 import android.os.IBinder; 11 import android.os.Message; 12 import android.os.Messenger; 13 import android.os.RemoteException; 14 import android.util.Log; 15 16 /** 17 * <p>HostApduService is a convenience {@link Service} class that can be 18 * extended to emulate an NFC card inside an Android 19 * service component. 20 * 21 * <div class="special reference"> 22 * <h3>Developer Guide</h3> 23 * For a general introduction into the topic of card emulation, 24 * please read the <a href="{@docRoot}guide/topics/nfc/ce.html"> 25 * NFC card emulation developer guide.</a></p> 26 * </div> 27 * 28 * <h3>NFC Protocols</h3> 29 * <p>Cards emulated by this class are based on the NFC-Forum ISO-DEP 30 * protocol (based on ISO/IEC 14443-4) and support processing 31 * command Application Protocol Data Units (APDUs) as 32 * defined in the ISO/IEC 7816-4 specification. 33 * 34 * <h3>Service selection</h3> 35 * <p>When a remote NFC device wants to talk to your 36 * service, it sends a so-called 37 * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification. 38 * The AID is an application identifier defined in ISO/IEC 7816-4. 39 * 40 * <p>The registration procedure for AIDs is defined in the 41 * ISO/IEC 7816-5 specification. If you don't want to register an 42 * AID, you are free to use AIDs in the proprietary range: 43 * bits 8-5 of the first byte must each be set to '1'. For example, 44 * "0xF00102030405" is a proprietary AID. If you do use proprietary 45 * AIDs, it is recommended to choose an AID of at least 6 bytes, 46 * to reduce the risk of collisions with other applications that 47 * might be using proprietary AIDs as well. 48 * 49 * <h3>AID groups</h3> 50 * <p>In some cases, a service may need to register multiple AIDs 51 * to implement a certain application, and it needs to be sure 52 * that it is the default handler for all of these AIDs (as opposed 53 * to some AIDs in the group going to another service). 54 * 55 * <p>An AID group is a list of AIDs that should be considered as 56 * belonging together by the OS. For all AIDs in an AID group, the 57 * OS will guarantee one of the following: 58 * <ul> 59 * <li>All AIDs in the group are routed to this service 60 * <li>No AIDs in the group are routed to this service 61 * </ul> 62 * In other words, there is no in-between state, where some AIDs 63 * in the group can be routed to this service, and some to another. 64 * <h3>AID groups and categories</h3> 65 * <p>Each AID group can be associated with a category. This allows 66 * the Android OS to classify services, and it allows the user to 67 * set defaults at the category level instead of the AID level. 68 * 69 * <p>You can use 70 * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)} 71 * to determine if your service is the default handler for a category. 72 * 73 * <p>In this version of the platform, the only known categories 74 * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}. 75 * AID groups without a category, or with a category that is not recognized 76 * by the current platform version, will automatically be 77 * grouped into the {@link CardEmulation#CATEGORY_OTHER} category. 78 * <h3>Service AID registration</h3> 79 * <p>To tell the platform which AIDs groups 80 * are requested by this service, a {@link #SERVICE_META_DATA} 81 * entry must be included in the declaration of the service. An 82 * example of a HostApduService manifest declaration is shown below: 83 * <pre> <service android:name=".MyHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"> 84 * <intent-filter> 85 * <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/> 86 * </intent-filter> 87 * <meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/> 88 * </service></pre> 89 * 90 * This meta-data tag points to an apduservice.xml file. 91 * An example of this file with a single AID group declaration is shown below: 92 * <pre> 93 * <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" 94 * android:description="@string/servicedesc" android:requireDeviceUnlock="false"> 95 * <aid-group android:description="@string/aiddescription" android:category="other"> 96 * <aid-filter android:name="F0010203040506"/> 97 * <aid-filter android:name="F0394148148100"/> 98 * </aid-group> 99 * </host-apdu-service> 100 * </pre> 101 * 102 * <p>The {@link android.R.styleable#HostApduService <host-apdu-service>} is required 103 * to contain a 104 * {@link android.R.styleable#HostApduService_description <android:description>} 105 * attribute that contains a user-friendly description of the service that may be shown in UI. 106 * The 107 * {@link android.R.styleable#HostApduService_requireDeviceUnlock <requireDeviceUnlock>} 108 * attribute can be used to specify that the device must be unlocked before this service 109 * can be invoked to handle APDUs. 110 * <p>The {@link android.R.styleable#HostApduService <host-apdu-service>} must 111 * contain one or more {@link android.R.styleable#AidGroup <aid-group>} tags. 112 * Each {@link android.R.styleable#AidGroup <aid-group>} must contain one or 113 * more {@link android.R.styleable#AidFilter <aid-filter>} tags, each of which 114 * contains a single AID. The AID must be specified in hexadecimal format, and contain 115 * an even number of characters. 116 * <h3>AID conflict resolution</h3> 117 * Multiple HostApduServices may be installed on a single device, and the same AID 118 * can be registered by more than one service. The Android platform resolves AID 119 * conflicts depending on which category an AID belongs to. Each category may 120 * have a different conflict resolution policy. For example, for some categories 121 * the user may be able to select a default service in the Android settings UI. 122 * For other categories, to policy may be to always ask the user which service 123 * is to be invoked in case of conflict. 124 * 125 * To query the conflict resolution policy for a certain category, see 126 * {@link CardEmulation#getSelectionModeForCategory(String)}. 127 * 128 * <h3>Data exchange</h3> 129 * <p>Once the platform has resolved a "SELECT AID" command APDU to a specific 130 * service component, the "SELECT AID" command APDU and all subsequent 131 * command APDUs will be sent to that service through 132 * {@link #processCommandApdu(byte[], Bundle)}, until either: 133 * <ul> 134 * <li>The NFC link is broken</li> 135 * <li>A "SELECT AID" APDU is received which resolves to another service</li> 136 * </ul> 137 * These two scenarios are indicated by a call to {@link #onDeactivated(int)}. 138 * 139 * <p class="note">Use of this class requires the 140 * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present 141 * on the device. 142 * 143 */ 144 public abstract class HostApduService extends Service { 145 /** 146 * The {@link Intent} action that must be declared as handled by the service. 147 */ 148 @SdkConstant(SdkConstantType.SERVICE_ACTION) 149 public static final String SERVICE_INTERFACE = 150 "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; 151 152 /** 153 * The name of the meta-data element that contains 154 * more information about this service. 155 */ 156 public static final String SERVICE_META_DATA = 157 "android.nfc.cardemulation.host_apdu_service"; 158 159 /** 160 * Reason for {@link #onDeactivated(int)}. 161 * Indicates deactivation was due to the NFC link 162 * being lost. 163 */ 164 public static final int DEACTIVATION_LINK_LOSS = 0; 165 166 /** 167 * Reason for {@link #onDeactivated(int)}. 168 * 169 * <p>Indicates deactivation was due to a different AID 170 * being selected (which implicitly deselects the AID 171 * currently active on the logical channel). 172 * 173 * <p>Note that this next AID may still be resolved to this 174 * service, in which case {@link #processCommandApdu(byte[], Bundle)} 175 * will be called again. 176 */ 177 public static final int DEACTIVATION_DESELECTED = 1; 178 179 static final String TAG = "ApduService"; 180 181 /** 182 * MSG_COMMAND_APDU is sent by NfcService when 183 * a 7816-4 command APDU has been received. 184 * 185 * @hide 186 */ 187 public static final int MSG_COMMAND_APDU = 0; 188 189 /** 190 * MSG_RESPONSE_APDU is sent to NfcService to send 191 * a response APDU back to the remote device. 192 * 193 * @hide 194 */ 195 public static final int MSG_RESPONSE_APDU = 1; 196 197 /** 198 * MSG_DEACTIVATED is sent by NfcService when 199 * the current session is finished; either because 200 * another AID was selected that resolved to 201 * another service, or because the NFC link 202 * was deactivated. 203 * 204 * @hide 205 */ 206 public static final int MSG_DEACTIVATED = 2; 207 208 /** 209 * 210 * @hide 211 */ 212 public static final int MSG_UNHANDLED = 3; 213 214 /** 215 * @hide 216 */ 217 public static final String KEY_DATA = "data"; 218 219 /** 220 * Messenger interface to NfcService for sending responses. 221 * Only accessed on main thread by the message handler. 222 * 223 * @hide 224 */ 225 Messenger mNfcService = null; 226 227 final Messenger mMessenger = new Messenger(new MsgHandler()); 228 229 final class MsgHandler extends Handler { 230 @Override 231 public void handleMessage(Message msg) { 232 switch (msg.what) { 233 case MSG_COMMAND_APDU: 234 Bundle dataBundle = msg.getData(); 235 if (dataBundle == null) { 236 return; 237 } 238 if (mNfcService == null) mNfcService = msg.replyTo; 239 240 byte[] apdu = dataBundle.getByteArray(KEY_DATA); 241 if (apdu != null) { 242 byte[] responseApdu = processCommandApdu(apdu, null); 243 if (responseApdu != null) { 244 if (mNfcService == null) { 245 Log.e(TAG, "Response not sent; service was deactivated."); 246 return; 247 } 248 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 249 Bundle responseBundle = new Bundle(); 250 responseBundle.putByteArray(KEY_DATA, responseApdu); 251 responseMsg.setData(responseBundle); 252 responseMsg.replyTo = mMessenger; 253 try { 254 mNfcService.send(responseMsg); 255 } catch (RemoteException e) { 256 Log.e("TAG", "Response not sent; RemoteException calling into " + 257 "NfcService."); 258 } 259 } 260 } else { 261 Log.e(TAG, "Received MSG_COMMAND_APDU without data."); 262 } 263 break; 264 case MSG_RESPONSE_APDU: 265 if (mNfcService == null) { 266 Log.e(TAG, "Response not sent; service was deactivated."); 267 return; 268 } 269 try { 270 msg.replyTo = mMessenger; 271 mNfcService.send(msg); 272 } catch (RemoteException e) { 273 Log.e(TAG, "RemoteException calling into NfcService."); 274 } 275 break; 276 case MSG_DEACTIVATED: 277 // Make sure we won't call into NfcService again 278 mNfcService = null; 279 onDeactivated(msg.arg1); 280 break; 281 case MSG_UNHANDLED: 282 if (mNfcService == null) { 283 Log.e(TAG, "notifyUnhandled not sent; service was deactivated."); 284 return; 285 } 286 try { 287 msg.replyTo = mMessenger; 288 mNfcService.send(msg); 289 } catch (RemoteException e) { 290 Log.e(TAG, "RemoteException calling into NfcService."); 291 } 292 break; 293 default: 294 super.handleMessage(msg); 295 } 296 } 297 } 298 299 @Override 300 public final IBinder onBind(Intent intent) { 301 return mMessenger.getBinder(); 302 } 303 304 /** 305 * Sends a response APDU back to the remote device. 306 * 307 * <p>Note: this method may be called from any thread and will not block. 308 * @param responseApdu A byte-array containing the reponse APDU. 309 */ 310 public final void sendResponseApdu(byte[] responseApdu) { 311 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 312 Bundle dataBundle = new Bundle(); 313 dataBundle.putByteArray(KEY_DATA, responseApdu); 314 responseMsg.setData(dataBundle); 315 try { 316 mMessenger.send(responseMsg); 317 } catch (RemoteException e) { 318 Log.e("TAG", "Local messenger has died."); 319 } 320 } 321 322 /** 323 * Calling this method allows the service to tell the OS 324 * that it won't be able to complete this transaction - 325 * for example, because it requires data connectivity 326 * that is not present at that moment. 327 * 328 * The OS may use this indication to give the user a list 329 * of alternative applications that can handle the last 330 * AID that was selected. If the user would select an 331 * application from the list, that action by itself 332 * will not cause the default to be changed; the selected 333 * application will be invoked for the next tap only. 334 * 335 * If there are no other applications that can handle 336 * this transaction, the OS will show an error dialog 337 * indicating your service could not complete the 338 * transaction. 339 * 340 * <p>Note: this method may be called anywhere between 341 * the first {@link #processCommandApdu(byte[], Bundle)} 342 * call and a {@link #onDeactivated(int)} call. 343 */ 344 public final void notifyUnhandled() { 345 Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED); 346 try { 347 mMessenger.send(unhandledMsg); 348 } catch (RemoteException e) { 349 Log.e("TAG", "Local messenger has died."); 350 } 351 } 352 353 354 /** 355 * <p>This method will be called when a command APDU has been received 356 * from a remote device. A response APDU can be provided directly 357 * by returning a byte-array in this method. Note that in general 358 * response APDUs must be sent as quickly as possible, given the fact 359 * that the user is likely holding his device over an NFC reader 360 * when this method is called. 361 * 362 * <p class="note">If there are multiple services that have registered for the same 363 * AIDs in their meta-data entry, you will only get called if the user has 364 * explicitly selected your service, either as a default or just for the next tap. 365 * 366 * <p class="note">This method is running on the main thread of your application. 367 * If you cannot return a response APDU immediately, return null 368 * and use the {@link #sendResponseApdu(byte[])} method later. 369 * 370 * @param commandApdu The APDU that was received from the remote device 371 * @param extras A bundle containing extra data. May be null. 372 * @return a byte-array containing the response APDU, or null if no 373 * response APDU can be sent at this point. 374 */ 375 public abstract byte[] processCommandApdu(byte[] commandApdu, Bundle extras); 376 377 /** 378 * This method will be called in two possible scenarios: 379 * <li>The NFC link has been deactivated or lost 380 * <li>A different AID has been selected and was resolved to a different 381 * service component 382 * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED} 383 */ 384 public abstract void onDeactivated(int reason); 385 } 386